From bfdf388802b0c3dd2f11d6831257ab1730e7c355 Mon Sep 17 00:00:00 2001 From: Dichen Zhang Date: Thu, 4 Jan 2024 18:55:36 +0000 Subject: [PATCH 001/465] Removed duplicade library Bug: 313791883 Test: build Change-Id: Ib4d58ebec02270f2c6dda4b307d03bd7e5526391 --- libs/ultrahdr/OWNERS | 3 - .../adobe-hdr-gain-map-license/Android.bp | 19 - .../adobe-hdr-gain-map-license/NOTICE | 1 - libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp | 73 - libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp | 333 --- libs/ultrahdr/gainmapmath.cpp | 775 ------- libs/ultrahdr/icc.cpp | 692 ------ libs/ultrahdr/include/ultrahdr/gainmapmath.h | 505 ---- libs/ultrahdr/include/ultrahdr/icc.h | 256 --- .../include/ultrahdr/jpegdecoderhelper.h | 143 -- .../include/ultrahdr/jpegencoderhelper.h | 102 - libs/ultrahdr/include/ultrahdr/jpegr.h | 452 ---- .../include/ultrahdr/jpegrerrorcode.h | 61 - libs/ultrahdr/include/ultrahdr/jpegrutils.h | 167 -- .../include/ultrahdr/multipictureformat.h | 64 - libs/ultrahdr/include/ultrahdr/ultrahdr.h | 82 - libs/ultrahdr/jpegdecoderhelper.cpp | 543 ----- libs/ultrahdr/jpegencoderhelper.cpp | 273 --- libs/ultrahdr/jpegr.cpp | 1503 ------------ libs/ultrahdr/jpegrutils.cpp | 600 ----- libs/ultrahdr/multipictureformat.cpp | 94 - libs/ultrahdr/tests/AndroidTest.xml | 26 - libs/ultrahdr/tests/data/jpeg_image.jpg | Bin 24430 -> 0 bytes libs/ultrahdr/tests/data/minnie-318x240.yu12 | 1930 ---------------- libs/ultrahdr/tests/data/minnie-320x240-y.jpg | Bin 20193 -> 0 bytes .../tests/data/minnie-320x240-yuv-icc.jpg | Bin 34266 -> 0 bytes .../tests/data/minnie-320x240-yuv.jpg | Bin 20193 -> 0 bytes libs/ultrahdr/tests/data/minnie-320x240.y | 1930 ---------------- libs/ultrahdr/tests/data/minnie-320x240.yu12 | 1930 ---------------- libs/ultrahdr/tests/data/raw_p010_image.p010 | Bin 2764800 -> 0 bytes .../tests/data/raw_yuv420_image.yuv420 | 1 - libs/ultrahdr/tests/gainmapmath_test.cpp | 1359 ----------- libs/ultrahdr/tests/icchelper_test.cpp | 77 - .../ultrahdr/tests/jpegdecoderhelper_test.cpp | 156 -- .../ultrahdr/tests/jpegencoderhelper_test.cpp | 135 -- libs/ultrahdr/tests/jpegr_test.cpp | 2035 ----------------- 36 files changed, 16320 deletions(-) delete mode 100644 libs/ultrahdr/OWNERS delete mode 100644 libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp delete mode 100644 libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE delete mode 100644 libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp delete mode 100644 libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp delete mode 100644 libs/ultrahdr/gainmapmath.cpp delete mode 100644 libs/ultrahdr/icc.cpp delete mode 100644 libs/ultrahdr/include/ultrahdr/gainmapmath.h delete mode 100644 libs/ultrahdr/include/ultrahdr/icc.h delete mode 100644 libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h delete mode 100644 libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h delete mode 100644 libs/ultrahdr/include/ultrahdr/jpegr.h delete mode 100644 libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h delete mode 100644 libs/ultrahdr/include/ultrahdr/jpegrutils.h delete mode 100644 libs/ultrahdr/include/ultrahdr/multipictureformat.h delete mode 100644 libs/ultrahdr/include/ultrahdr/ultrahdr.h delete mode 100644 libs/ultrahdr/jpegdecoderhelper.cpp delete mode 100644 libs/ultrahdr/jpegencoderhelper.cpp delete mode 100644 libs/ultrahdr/jpegr.cpp delete mode 100644 libs/ultrahdr/jpegrutils.cpp delete mode 100644 libs/ultrahdr/multipictureformat.cpp delete mode 100644 libs/ultrahdr/tests/AndroidTest.xml delete mode 100644 libs/ultrahdr/tests/data/jpeg_image.jpg delete mode 100644 libs/ultrahdr/tests/data/minnie-318x240.yu12 delete mode 100644 libs/ultrahdr/tests/data/minnie-320x240-y.jpg delete mode 100644 libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg delete mode 100644 libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg delete mode 100644 libs/ultrahdr/tests/data/minnie-320x240.y delete mode 100644 libs/ultrahdr/tests/data/minnie-320x240.yu12 delete mode 100644 libs/ultrahdr/tests/data/raw_p010_image.p010 delete mode 100644 libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 delete mode 100644 libs/ultrahdr/tests/gainmapmath_test.cpp delete mode 100644 libs/ultrahdr/tests/icchelper_test.cpp delete mode 100644 libs/ultrahdr/tests/jpegdecoderhelper_test.cpp delete mode 100644 libs/ultrahdr/tests/jpegencoderhelper_test.cpp delete mode 100644 libs/ultrahdr/tests/jpegr_test.cpp diff --git a/libs/ultrahdr/OWNERS b/libs/ultrahdr/OWNERS deleted file mode 100644 index 6ace354d0b..0000000000 --- a/libs/ultrahdr/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -arifdikici@google.com -dichenzhang@google.com -kyslov@google.com \ No newline at end of file diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp deleted file mode 100644 index 2fa361f0b7..0000000000 --- a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2023 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -license { - name: "adobe_hdr_gain_map_license-deprecated", - license_kinds: ["legacy_by_exception_only"], - license_text: ["NOTICE"], -} diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE deleted file mode 100644 index 3f6c5944c7..0000000000 --- a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE +++ /dev/null @@ -1 +0,0 @@ -This product includes Gain Map technology under license by Adobe. diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp deleted file mode 100644 index f1f403548d..0000000000 --- a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// System include files -#include -#include -#include - -// User include files -#include "ultrahdr/jpegr.h" - -using namespace android::ultrahdr; - -// Transfer functions for image data, sync with ultrahdr.h -const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1; -const int kOfMax = ULTRAHDR_OUTPUT_MAX; - -class UltraHdrDecFuzzer { -public: - UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; - void process(); - -private: - FuzzedDataProvider mFdp; -}; - -void UltraHdrDecFuzzer::process() { - // hdr_of - auto of = static_cast(mFdp.ConsumeIntegralInRange(kOfMin, kOfMax)); - auto buffer = mFdp.ConsumeRemainingBytes(); - jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(), - ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - - std::vector iccData(0); - std::vector exifData(0); - jpegr_info_struct info{0, 0, &iccData, &exifData}; - JpegR jpegHdr; - (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info); -//#define DUMP_PARAM -#ifdef DUMP_PARAM - std::cout << "input buffer size " << jpegImgR.length << std::endl; - std::cout << "image dimensions " << info.width << " x " << info.width << std::endl; -#endif - size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); - jpegr_uncompressed_struct decodedJpegR; - auto decodedRaw = std::make_unique(outSize); - decodedJpegR.data = decodedRaw.get(); - ultrahdr_metadata_struct metadata; - jpegr_uncompressed_struct decodedGainMap{}; - (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, - mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX), nullptr, of, - &decodedGainMap, &metadata); - if (decodedGainMap.data) free(decodedGainMap.data); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - UltraHdrDecFuzzer fuzzHandle(data, size); - fuzzHandle.process(); - return 0; -} diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp deleted file mode 100644 index 2d59e8bb88..0000000000 --- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// System include files -#include -#include -#include -#include -#include - -// User include files -#include "ultrahdr/gainmapmath.h" -#include "ultrahdr/jpegdecoderhelper.h" -#include "ultrahdr/jpegencoderhelper.h" -#include "utils/Log.h" - -using namespace android::ultrahdr; - -// Color gamuts for image data, sync with ultrahdr.h -const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1; -const int kCgMax = ULTRAHDR_COLORGAMUT_MAX; - -// Transfer functions for image data, sync with ultrahdr.h -const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1; -const int kTfMax = ULTRAHDR_TF_PQ; - -// Transfer functions for image data, sync with ultrahdr.h -const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1; -const int kOfMax = ULTRAHDR_OUTPUT_MAX; - -// quality factor -const int kQfMin = 0; -const int kQfMax = 100; - -class UltraHdrEncFuzzer { -public: - UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; - void process(); - void fillP010Buffer(uint16_t* data, int width, int height, int stride); - void fill420Buffer(uint8_t* data, int width, int height, int stride); - -private: - FuzzedDataProvider mFdp; -}; - -void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) { - uint16_t* tmp = data; - std::vector buffer(16); - for (int i = 0; i < buffer.size(); i++) { - buffer[i] = (mFdp.ConsumeIntegralInRange(0, (1 << 10) - 1)) << 6; - } - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i += buffer.size()) { - memcpy(tmp + i, buffer.data(), - std::min((int)buffer.size(), (width - i)) * sizeof(*data)); - std::shuffle(buffer.begin(), buffer.end(), - std::default_random_engine(std::random_device{}())); - } - tmp += stride; - } -} - -void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) { - uint8_t* tmp = data; - std::vector buffer(16); - mFdp.ConsumeData(buffer.data(), buffer.size()); - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i += buffer.size()) { - memcpy(tmp + i, buffer.data(), - std::min((int)buffer.size(), (width - i)) * sizeof(*data)); - std::shuffle(buffer.begin(), buffer.end(), - std::default_random_engine(std::random_device{}())); - } - tmp += stride; - } -} - -void UltraHdrEncFuzzer::process() { - while (mFdp.remaining_bytes()) { - struct jpegr_uncompressed_struct p010Img {}; - struct jpegr_uncompressed_struct yuv420Img {}; - struct jpegr_uncompressed_struct grayImg {}; - struct jpegr_compressed_struct jpegImgR {}; - struct jpegr_compressed_struct jpegImg {}; - struct jpegr_compressed_struct jpegGainMap {}; - - // which encode api to select - int muxSwitch = mFdp.ConsumeIntegralInRange(0, 4); - - // quality factor - int quality = mFdp.ConsumeIntegralInRange(kQfMin, kQfMax); - - // hdr_tf - auto tf = static_cast( - mFdp.ConsumeIntegralInRange(kTfMin, kTfMax)); - - // p010 Cg - auto p010Cg = - static_cast(mFdp.ConsumeIntegralInRange(kCgMin, kCgMax)); - - // 420 Cg - auto yuv420Cg = - static_cast(mFdp.ConsumeIntegralInRange(kCgMin, kCgMax)); - - // hdr_of - auto of = static_cast( - mFdp.ConsumeIntegralInRange(kOfMin, kOfMax)); - - int width = mFdp.ConsumeIntegralInRange(kMinWidth, kMaxWidth); - width = (width >> 1) << 1; - - int height = mFdp.ConsumeIntegralInRange(kMinHeight, kMaxHeight); - height = (height >> 1) << 1; - - std::unique_ptr bufferYHdr = nullptr; - std::unique_ptr bufferUVHdr = nullptr; - std::unique_ptr bufferYSdr = nullptr; - std::unique_ptr bufferUVSdr = nullptr; - std::unique_ptr grayImgRaw = nullptr; - if (muxSwitch != 4) { - // init p010 image - bool isUVContiguous = mFdp.ConsumeBool(); - bool hasYStride = mFdp.ConsumeBool(); - int yStride = hasYStride ? mFdp.ConsumeIntegralInRange(width, width + 128) : width; - p010Img.width = width; - p010Img.height = height; - p010Img.colorGamut = p010Cg; - p010Img.luma_stride = hasYStride ? yStride : 0; - int bppP010 = 2; - if (isUVContiguous) { - size_t p010Size = yStride * height * 3 / 2; - bufferYHdr = std::make_unique(p010Size); - p010Img.data = bufferYHdr.get(); - p010Img.chroma_data = nullptr; - p010Img.chroma_stride = 0; - fillP010Buffer(bufferYHdr.get(), width, height, yStride); - fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride); - } else { - int uvStride = mFdp.ConsumeIntegralInRange(width, width + 128); - size_t p010YSize = yStride * height; - bufferYHdr = std::make_unique(p010YSize); - p010Img.data = bufferYHdr.get(); - fillP010Buffer(bufferYHdr.get(), width, height, yStride); - size_t p010UVSize = uvStride * p010Img.height / 2; - bufferUVHdr = std::make_unique(p010UVSize); - p010Img.chroma_data = bufferUVHdr.get(); - p010Img.chroma_stride = uvStride; - fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride); - } - } else { - size_t map_width = width / kMapDimensionScaleFactor; - size_t map_height = height / kMapDimensionScaleFactor; - // init 400 image - grayImg.width = map_width; - grayImg.height = map_height; - grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - - const size_t graySize = map_width * map_height; - grayImgRaw = std::make_unique(graySize); - grayImg.data = grayImgRaw.get(); - fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width); - grayImg.chroma_data = nullptr; - grayImg.luma_stride = 0; - grayImg.chroma_stride = 0; - } - - if (muxSwitch > 0) { - // init 420 image - bool isUVContiguous = mFdp.ConsumeBool(); - bool hasYStride = mFdp.ConsumeBool(); - int yStride = hasYStride ? mFdp.ConsumeIntegralInRange(width, width + 128) : width; - yuv420Img.width = width; - yuv420Img.height = height; - yuv420Img.colorGamut = yuv420Cg; - yuv420Img.luma_stride = hasYStride ? yStride : 0; - if (isUVContiguous) { - size_t yuv420Size = yStride * height * 3 / 2; - bufferYSdr = std::make_unique(yuv420Size); - yuv420Img.data = bufferYSdr.get(); - yuv420Img.chroma_data = nullptr; - yuv420Img.chroma_stride = 0; - fill420Buffer(bufferYSdr.get(), width, height, yStride); - fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2, - yStride / 2); - fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2, - yStride / 2); - } else { - int uvStride = mFdp.ConsumeIntegralInRange(width / 2, width / 2 + 128); - size_t yuv420YSize = yStride * height; - bufferYSdr = std::make_unique(yuv420YSize); - yuv420Img.data = bufferYSdr.get(); - fill420Buffer(bufferYSdr.get(), width, height, yStride); - size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2; - bufferUVSdr = std::make_unique(yuv420UVSize); - yuv420Img.chroma_data = bufferUVSdr.get(); - yuv420Img.chroma_stride = uvStride; - fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride); - fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2, - uvStride); - } - } - - // dest - // 2 * p010 size as input data is random, DCT compression might not behave as expected - jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2); - auto jpegImgRaw = std::make_unique(jpegImgR.maxLength); - jpegImgR.data = jpegImgRaw.get(); - -//#define DUMP_PARAM -#ifdef DUMP_PARAM - std::cout << "Api Select " << muxSwitch << std::endl; - std::cout << "image dimensions " << width << " x " << height << std::endl; - std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl; - std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl; - std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl; - std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl; - std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl; - std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl; - std::cout << "quality factor " << quality << std::endl; -#endif - - JpegR jpegHdr; - android::status_t status = android::UNKNOWN_ERROR; - if (muxSwitch == 0) { // api 0 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr); - } else if (muxSwitch == 1) { // api 1 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr); - } else { - // compressed img - JpegEncoderHelper encoder; - struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img; - if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width; - if (!yuv420ImgCopy.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420Img.data); - yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height; - yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1; - } - - if (encoder.compressImage(reinterpret_cast(yuv420ImgCopy.data), - reinterpret_cast(yuv420ImgCopy.chroma_data), - yuv420ImgCopy.width, yuv420ImgCopy.height, - yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride, - quality, nullptr, 0)) { - jpegImg.length = encoder.getCompressedImageSize(); - jpegImg.maxLength = jpegImg.length; - jpegImg.data = encoder.getCompressedImagePtr(); - jpegImg.colorGamut = yuv420Cg; - - if (muxSwitch == 2) { // api 2 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR); - } else if (muxSwitch == 3) { // api 3 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR); - } else if (muxSwitch == 4) { // api 4 - jpegImgR.length = 0; - JpegEncoderHelper gainMapEncoder; - if (gainMapEncoder.compressImage(reinterpret_cast(grayImg.data), - nullptr, grayImg.width, grayImg.height, - grayImg.width, 0, quality, nullptr, 0)) { - jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); - jpegGainMap.maxLength = jpegImg.length; - jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); - jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ultrahdr_metadata_struct metadata; - metadata.version = kJpegrVersion; - if (tf == ULTRAHDR_TF_HLG) { - metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits; - } else if (tf == ULTRAHDR_TF_PQ) { - metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits; - } else { - metadata.maxContentBoost = 1.0f; - } - metadata.minContentBoost = 1.0f; - metadata.gamma = 1.0f; - metadata.offsetSdr = 0.0f; - metadata.offsetHdr = 0.0f; - metadata.hdrCapacityMin = 1.0f; - metadata.hdrCapacityMax = metadata.maxContentBoost; - status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); - } - } - } - } - if (status == android::OK) { - std::vector iccData(0); - std::vector exifData(0); - jpegr_info_struct info{0, 0, &iccData, &exifData}; - status = jpegHdr.getJPEGRInfo(&jpegImgR, &info); - if (status == android::OK) { - size_t outSize = - info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); - jpegr_uncompressed_struct decodedJpegR; - auto decodedRaw = std::make_unique(outSize); - decodedJpegR.data = decodedRaw.get(); - ultrahdr_metadata_struct metadata; - jpegr_uncompressed_struct decodedGainMap{}; - status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR, - mFdp.ConsumeFloatingPointInRange(1.0, FLT_MAX), - nullptr, of, &decodedGainMap, &metadata); - if (status != android::OK) { - ALOGE("encountered error during decoding %d", status); - } - if (decodedGainMap.data) free(decodedGainMap.data); - } else { - ALOGE("encountered error during get jpeg info %d", status); - } - } else { - ALOGE("encountered error during encoding %d", status); - } - } -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - UltraHdrEncFuzzer fuzzHandle(data, size); - fuzzHandle.process(); - return 0; -} diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp deleted file mode 100644 index ae9c4ca338..0000000000 --- a/libs/ultrahdr/gainmapmath.cpp +++ /dev/null @@ -1,775 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include - -namespace android::ultrahdr { - -static const std::vector kPqOETF = [] { - std::vector result; - for (int idx = 0; idx < kPqOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kPqOETFNumEntries - 1); - result.push_back(pqOetf(value)); - } - return result; -}(); - -static const std::vector kPqInvOETF = [] { - std::vector result; - for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kPqInvOETFNumEntries - 1); - result.push_back(pqInvOetf(value)); - } - return result; -}(); - -static const std::vector kHlgOETF = [] { - std::vector result; - for (int idx = 0; idx < kHlgOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kHlgOETFNumEntries - 1); - result.push_back(hlgOetf(value)); - } - return result; -}(); - -static const std::vector kHlgInvOETF = [] { - std::vector result; - for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kHlgInvOETFNumEntries - 1); - result.push_back(hlgInvOetf(value)); - } - return result; -}(); - -static const std::vector kSrgbInvOETF = [] { - std::vector result; - for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kSrgbInvOETFNumEntries - 1); - result.push_back(srgbInvOetf(value)); - } - return result; -}(); - -// Use Shepard's method for inverse distance weighting. For more information: -// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method - -float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) { - return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1)); -} - -void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) { - for (int y = 0; y < mMapScaleFactor; y++) { - for (int x = 0; x < mMapScaleFactor; x++) { - float pos_x = ((float)x) / mMapScaleFactor; - float pos_y = ((float)y) / mMapScaleFactor; - int curr_x = floor(pos_x); - int curr_y = floor(pos_y); - int next_x = curr_x + incR; - int next_y = curr_y + incB; - float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y); - int index = y * mMapScaleFactor * 4 + x * 4; - if (e1_distance == 0) { - weights[index++] = 1.f; - weights[index++] = 0.f; - weights[index++] = 0.f; - weights[index++] = 0.f; - } else { - float e1_weight = 1.f / e1_distance; - - float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y); - float e2_weight = 1.f / e2_distance; - - float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y); - float e3_weight = 1.f / e3_distance; - - float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y); - float e4_weight = 1.f / e4_distance; - - float total_weight = e1_weight + e2_weight + e3_weight + e4_weight; - - weights[index++] = e1_weight / total_weight; - weights[index++] = e2_weight / total_weight; - weights[index++] = e3_weight / total_weight; - weights[index++] = e4_weight / total_weight; - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// sRGB transformations - -static const float kMaxPixelFloat = 1.0f; -static float clampPixelFloat(float value) { - return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value; -} - -// See IEC 61966-2-1/Amd 1:2003, Equation F.7. -static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f; - -float srgbLuminance(Color e) { - return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; -} - -// See ITU-R BT.709-6, Section 3. -// Uses the same coefficients for deriving luma signal as -// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance -// function above. -static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f; - -Color srgbRgbToYuv(Color e_gamma) { - float y_gamma = srgbLuminance(e_gamma); - return {{{ y_gamma, - (e_gamma.b - y_gamma) / kSrgbCb, - (e_gamma.r - y_gamma) / kSrgbCr }}}; -} - -// See ITU-R BT.709-6, Section 3. -// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we -// can reuse the luminance coefficients since they are the same. -static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG; -static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG; - -Color srgbYuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v), - clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}}; -} - -// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6. -float srgbInvOetf(float e_gamma) { - if (e_gamma <= 0.04045f) { - return e_gamma / 12.92f; - } else { - return pow((e_gamma + 0.055f) / 1.055f, 2.4); - } -} - -Color srgbInvOetf(Color e_gamma) { - return {{{ srgbInvOetf(e_gamma.r), - srgbInvOetf(e_gamma.g), - srgbInvOetf(e_gamma.b) }}}; -} - -// See IEC 61966-2-1, Equations F.5 and F.6. -float srgbInvOetfLUT(float e_gamma) { - uint32_t value = static_cast(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1); - return kSrgbInvOETF[value]; -} - -Color srgbInvOetfLUT(Color e_gamma) { - return {{{ srgbInvOetfLUT(e_gamma.r), - srgbInvOetfLUT(e_gamma.g), - srgbInvOetfLUT(e_gamma.b) }}}; -} - -//////////////////////////////////////////////////////////////////////////////// -// Display-P3 transformations - -// See SMPTE EG 432-1, Equation 7-8. -static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f; - -float p3Luminance(Color e) { - return kP3R * e.r + kP3G * e.g + kP3B * e.b; -} - -// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. -// Unfortunately, calculation of luma signal differs from calculation of -// luminance for Display-P3, so we can't reuse p3Luminance here. -static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f; -static const float kP3Cb = 1.772f, kP3Cr = 1.402f; - -Color p3RgbToYuv(Color e_gamma) { - float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b; - return {{{ y_gamma, - (e_gamma.b - y_gamma) / kP3Cb, - (e_gamma.r - y_gamma) / kP3Cr }}}; -} - -// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. -// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must -// use luma signal coefficients rather than the luminance coefficients. -static const float kP3GCb = kP3YB * kP3Cb / kP3YG; -static const float kP3GCr = kP3YR * kP3Cr / kP3YG; - -Color p3YuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v), - clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}}; -} - - -//////////////////////////////////////////////////////////////////////////////// -// BT.2100 transformations - according to ITU-R BT.2100-2 - -// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF -static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f; - -float bt2100Luminance(Color e) { - return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; -} - -// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. -// BT.2100 uses the same coefficients for calculating luma signal and luminance, -// so we reuse the luminance function here. -static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; - -Color bt2100RgbToYuv(Color e_gamma) { - float y_gamma = bt2100Luminance(e_gamma); - return {{{ y_gamma, - (e_gamma.b - y_gamma) / kBt2100Cb, - (e_gamma.r - y_gamma) / kBt2100Cr }}}; -} - -// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. -// -// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients. -// -// Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty -// straight forward; we just invert the formulas for U and V above. But deriving -// the formula for G is a bit more complicated: -// -// Start with equation for luminance: -// Y = kBt2100R * R + kBt2100G * G + kBt2100B * B -// Solve for G: -// G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B -// Substitute equations for R and B in terms YUV: -// G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B -// Simplify: -// G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G) -// + U * (kBt2100B * kBt2100Cb / kBt2100G) -// + V * (kBt2100R * kBt2100Cr / kBt2100G) -// -// We then get the following coeficients for calculating G from YUV: -// -// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1 -// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645 -// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713 - -static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G; -static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G; - -Color bt2100YuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v), - clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}}; -} - -// See ITU-R BT.2100-2, Table 5, HLG Reference OETF. -static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073; - -float hlgOetf(float e) { - if (e <= 1.0f/12.0f) { - return sqrt(3.0f * e); - } else { - return kHlgA * log(12.0f * e - kHlgB) + kHlgC; - } -} - -Color hlgOetf(Color e) { - return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}}; -} - -float hlgOetfLUT(float e) { - uint32_t value = static_cast(e * (kHlgOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kHlgOETFNumEntries - 1); - - return kHlgOETF[value]; -} - -Color hlgOetfLUT(Color e) { - return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}}; -} - -// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF. -float hlgInvOetf(float e_gamma) { - if (e_gamma <= 0.5f) { - return pow(e_gamma, 2.0f) / 3.0f; - } else { - return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f; - } -} - -Color hlgInvOetf(Color e_gamma) { - return {{{ hlgInvOetf(e_gamma.r), - hlgInvOetf(e_gamma.g), - hlgInvOetf(e_gamma.b) }}}; -} - -float hlgInvOetfLUT(float e_gamma) { - uint32_t value = static_cast(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1); - - return kHlgInvOETF[value]; -} - -Color hlgInvOetfLUT(Color e_gamma) { - return {{{ hlgInvOetfLUT(e_gamma.r), - hlgInvOetfLUT(e_gamma.g), - hlgInvOetfLUT(e_gamma.b) }}}; -} - -// See ITU-R BT.2100-2, Table 4, Reference PQ OETF. -static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f; -static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f, - kPqC3 = 2392.0f / 4096.0f * 32.0f; - -float pqOetf(float e) { - if (e <= 0.0f) return 0.0f; - return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)), - kPqM2); -} - -Color pqOetf(Color e) { - return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}}; -} - -float pqOetfLUT(float e) { - uint32_t value = static_cast(e * (kPqOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kPqOETFNumEntries - 1); - - return kPqOETF[value]; -} - -Color pqOetfLUT(Color e) { - return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}}; -} - -// Derived from the inverse of the Reference PQ OETF. -static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f, - kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f; - -float pqInvOetf(float e_gamma) { - // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't - // always catch 0.0. So, check on 0.0001, since anything this small will - // effectively be crushed to zero anyways. - if (e_gamma <= 0.0001f) return 0.0f; - return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB) - / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)), - kPqInvE); -} - -Color pqInvOetf(Color e_gamma) { - return {{{ pqInvOetf(e_gamma.r), - pqInvOetf(e_gamma.g), - pqInvOetf(e_gamma.b) }}}; -} - -float pqInvOetfLUT(float e_gamma) { - uint32_t value = static_cast(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place - value = CLIP3(value, 0, kPqInvOETFNumEntries - 1); - - return kPqInvOETF[value]; -} - -Color pqInvOetfLUT(Color e_gamma) { - return {{{ pqInvOetfLUT(e_gamma.r), - pqInvOetfLUT(e_gamma.g), - pqInvOetfLUT(e_gamma.b) }}}; -} - - -//////////////////////////////////////////////////////////////////////////////// -// Color conversions - -Color bt709ToP3(Color e) { - return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b, - 0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b, - 0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}}; -} - -Color bt709ToBt2100(Color e) { - return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b, - 0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b, - 0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}}; -} - -Color p3ToBt709(Color e) { - return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b, - -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b, - -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}}; -} - -Color p3ToBt2100(Color e) { - return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b, - 0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b, - -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}}; -} - -Color bt2100ToBt709(Color e) { - return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b, - -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b, - -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}}; -} - -Color bt2100ToP3(Color e) { - return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b, - -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b, - 0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b - }}}; -} - -// TODO: confirm we always want to convert like this before calculating -// luminance. -ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, - ultrahdr_color_gamut hdr_gamut) { - switch (sdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - switch (hdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - return identityConversion; - case ULTRAHDR_COLORGAMUT_P3: - return p3ToBt709; - case ULTRAHDR_COLORGAMUT_BT2100: - return bt2100ToBt709; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - return nullptr; - } - break; - case ULTRAHDR_COLORGAMUT_P3: - switch (hdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - return bt709ToP3; - case ULTRAHDR_COLORGAMUT_P3: - return identityConversion; - case ULTRAHDR_COLORGAMUT_BT2100: - return bt2100ToP3; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - return nullptr; - } - break; - case ULTRAHDR_COLORGAMUT_BT2100: - switch (hdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - return bt709ToBt2100; - case ULTRAHDR_COLORGAMUT_P3: - return p3ToBt2100; - case ULTRAHDR_COLORGAMUT_BT2100: - return identityConversion; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - return nullptr; - } - break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - return nullptr; - } -} - -// All of these conversions are derived from the respective input YUV->RGB conversion followed by -// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this -// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match -// DataSpace. - -Color yuv709To601(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + 0.101579f * e_gamma.u + 0.196076f * e_gamma.v, - 0.0f * e_gamma.y + 0.989854f * e_gamma.u + -0.110653f * e_gamma.v, - 0.0f * e_gamma.y + -0.072453f * e_gamma.u + 0.983398f * e_gamma.v }}}; -} - -Color yuv709To2100(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u + 0.096312f * e_gamma.v, - 0.0f * e_gamma.y + 0.995306f * e_gamma.u + -0.051192f * e_gamma.v, - 0.0f * e_gamma.y + 0.011507f * e_gamma.u + 1.002637f * e_gamma.v }}}; -} - -Color yuv601To709(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v, - 0.0f * e_gamma.y + 1.018640f * e_gamma.u + 0.114618f * e_gamma.v, - 0.0f * e_gamma.y + 0.075049f * e_gamma.u + 1.025327f * e_gamma.v }}}; -} - -Color yuv601To2100(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v, - 0.0f * e_gamma.y + 1.010016f * e_gamma.u + 0.061592f * e_gamma.v, - 0.0f * e_gamma.y + 0.086969f * e_gamma.u + 1.029350f * e_gamma.v }}}; -} - -Color yuv2100To709(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + 0.018149f * e_gamma.u + -0.095132f * e_gamma.v, - 0.0f * e_gamma.y + 1.004123f * e_gamma.u + 0.051267f * e_gamma.v, - 0.0f * e_gamma.y + -0.011524f * e_gamma.u + 0.996782f * e_gamma.v }}}; -} - -Color yuv2100To601(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + 0.117887f * e_gamma.u + 0.105521f * e_gamma.v, - 0.0f * e_gamma.y + 0.995211f * e_gamma.u + -0.059549f * e_gamma.v, - 0.0f * e_gamma.y + -0.084085f * e_gamma.u + 0.976518f * e_gamma.v }}}; -} - -void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma, - ColorTransformFn fn) { - Color yuv1 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 ); - Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 ); - Color yuv3 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 + 1); - Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1); - - yuv1 = fn(yuv1); - yuv2 = fn(yuv2); - yuv3 = fn(yuv3); - yuv4 = fn(yuv4); - - Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f; - - size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->luma_stride; - size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->luma_stride; - size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->luma_stride; - size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride; - - uint8_t& y1_uint = reinterpret_cast(image->data)[pixel_y1_idx]; - uint8_t& y2_uint = reinterpret_cast(image->data)[pixel_y2_idx]; - uint8_t& y3_uint = reinterpret_cast(image->data)[pixel_y3_idx]; - uint8_t& y4_uint = reinterpret_cast(image->data)[pixel_y4_idx]; - - size_t pixel_count = image->chroma_stride * image->height / 2; - size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride); - - uint8_t& u_uint = reinterpret_cast(image->chroma_data)[pixel_uv_idx]; - uint8_t& v_uint = reinterpret_cast(image->chroma_data)[pixel_count + pixel_uv_idx]; - - y1_uint = static_cast(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255)); - y2_uint = static_cast(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255)); - y3_uint = static_cast(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255)); - y4_uint = static_cast(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255)); - - u_uint = static_cast(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255)); - v_uint = static_cast(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255)); -} - -//////////////////////////////////////////////////////////////////////////////// -// Gain map calculations -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) { - return encodeGain(y_sdr, y_hdr, metadata, - log2(metadata->minContentBoost), log2(metadata->maxContentBoost)); -} - -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, - float log2MinContentBoost, float log2MaxContentBoost) { - float gain = 1.0f; - if (y_sdr > 0.0f) { - gain = y_hdr / y_sdr; - } - - if (gain < metadata->minContentBoost) gain = metadata->minContentBoost; - if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost; - - return static_cast((log2(gain) - log2MinContentBoost) - / (log2MaxContentBoost - log2MinContentBoost) - * 255.0f); -} - -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) { - float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) - + log2(metadata->maxContentBoost) * gain; - float gainFactor = exp2(logBoost); - return e * gainFactor; -} - -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) { - float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) - + log2(metadata->maxContentBoost) * gain; - float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); - return e * gainFactor; -} - -Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) { - float gainFactor = gainLUT.getGainFactor(gain); - return e * gainFactor; -} - -Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { - uint8_t* luma_data = reinterpret_cast(image->data); - size_t luma_stride = image->luma_stride; - uint8_t* chroma_data = reinterpret_cast(image->chroma_data); - size_t chroma_stride = image->chroma_stride; - - size_t offset_cr = chroma_stride * (image->height / 2); - size_t pixel_y_idx = x + y * luma_stride; - size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride; - - uint8_t y_uint = luma_data[pixel_y_idx]; - uint8_t u_uint = chroma_data[pixel_chroma_idx]; - uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx]; - - // 128 bias for UV given we are using jpeglib; see: - // https://github.com/kornelski/libjpeg/blob/master/structure.doc - return {{{ static_cast(y_uint) / 255.0f, - (static_cast(u_uint) - 128.0f) / 255.0f, - (static_cast(v_uint) - 128.0f) / 255.0f }}}; -} - -Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { - uint16_t* luma_data = reinterpret_cast(image->data); - size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride; - uint16_t* chroma_data = reinterpret_cast(image->chroma_data); - size_t chroma_stride = image->chroma_stride; - - size_t pixel_y_idx = y * luma_stride + x; - size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1); - size_t pixel_v_idx = pixel_u_idx + 1; - - uint16_t y_uint = luma_data[pixel_y_idx] >> 6; - uint16_t u_uint = chroma_data[pixel_u_idx] >> 6; - uint16_t v_uint = chroma_data[pixel_v_idx] >> 6; - - // Conversions include taking narrow-range into account. - return {{{ (static_cast(y_uint) - 64.0f) / 876.0f, - (static_cast(u_uint) - 64.0f) / 896.0f - 0.5f, - (static_cast(v_uint) - 64.0f) / 896.0f - 0.5f }}}; -} - -typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t); - -static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y, - getPixelFn get_pixel_fn) { - Color e = {{{ 0.0f, 0.0f, 0.0f }}}; - for (size_t dy = 0; dy < map_scale_factor; ++dy) { - for (size_t dx = 0; dx < map_scale_factor; ++dx) { - e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy); - } - } - - return e / static_cast(map_scale_factor * map_scale_factor); -} - -Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { - return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel); -} - -Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { - return samplePixels(image, map_scale_factor, x, y, getP010Pixel); -} - -// TODO: do we need something more clever for filtering either the map or images -// to generate the map? - -static size_t clamp(const size_t& val, const size_t& low, const size_t& high) { - return val < low ? low : (high < val ? high : val); -} - -static float mapUintToFloat(uint8_t map_uint) { - return static_cast(map_uint) / 255.0f; -} - -static float pythDistance(float x_diff, float y_diff) { - return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f)); -} - -// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following. -float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) { - float x_map = static_cast(x) / map_scale_factor; - float y_map = static_cast(y) / map_scale_factor; - - size_t x_lower = static_cast(floor(x_map)); - size_t x_upper = x_lower + 1; - size_t y_lower = static_cast(floor(y_map)); - size_t y_upper = y_lower + 1; - - x_lower = clamp(x_lower, 0, map->width - 1); - x_upper = clamp(x_upper, 0, map->width - 1); - y_lower = clamp(y_lower, 0, map->height - 1); - y_upper = clamp(y_upper, 0, map->height - 1); - - // Use Shepard's method for inverse distance weighting. For more information: - // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method - - float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); - float e1_dist = pythDistance(x_map - static_cast(x_lower), - y_map - static_cast(y_lower)); - if (e1_dist == 0.0f) return e1; - - float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); - float e2_dist = pythDistance(x_map - static_cast(x_lower), - y_map - static_cast(y_upper)); - if (e2_dist == 0.0f) return e2; - - float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); - float e3_dist = pythDistance(x_map - static_cast(x_upper), - y_map - static_cast(y_lower)); - if (e3_dist == 0.0f) return e3; - - float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); - float e4_dist = pythDistance(x_map - static_cast(x_upper), - y_map - static_cast(y_upper)); - if (e4_dist == 0.0f) return e2; - - float e1_weight = 1.0f / e1_dist; - float e2_weight = 1.0f / e2_dist; - float e3_weight = 1.0f / e3_dist; - float e4_weight = 1.0f / e4_dist; - float total_weight = e1_weight + e2_weight + e3_weight + e4_weight; - - return e1 * (e1_weight / total_weight) - + e2 * (e2_weight / total_weight) - + e3 * (e3_weight / total_weight) - + e4 * (e4_weight / total_weight); -} - -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, - ShepardsIDW& weightTables) { - // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the - // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor) - int x_lower = x / map_scale_factor; - int x_upper = x_lower + 1; - int y_lower = y / map_scale_factor; - int y_upper = y_lower + 1; - - x_lower = std::min(x_lower, map->width - 1); - x_upper = std::min(x_upper, map->width - 1); - y_lower = std::min(y_lower, map->height - 1); - y_upper = std::min(y_upper, map->height - 1); - - float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); - float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); - float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); - float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); - - // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the - // following by using & (map_scale_factor - 1) - int offset_x = x % map_scale_factor; - int offset_y = y % map_scale_factor; - - float* weights = weightTables.mWeights; - if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC; - else if (x_lower == x_upper) weights = weightTables.mWeightsNR; - else if (y_lower == y_upper) weights = weightTables.mWeightsNB; - weights += offset_y * map_scale_factor * 4 + offset_x * 4; - - return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3]; -} - -uint32_t colorToRgba1010102(Color e_gamma) { - return (0x3ff & static_cast(e_gamma.r * 1023.0f)) - | ((0x3ff & static_cast(e_gamma.g * 1023.0f)) << 10) - | ((0x3ff & static_cast(e_gamma.b * 1023.0f)) << 20) - | (0x3 << 30); // Set alpha to 1.0 -} - -uint64_t colorToRgbaF16(Color e_gamma) { - return (uint64_t) floatToHalf(e_gamma.r) - | (((uint64_t) floatToHalf(e_gamma.g)) << 16) - | (((uint64_t) floatToHalf(e_gamma.b)) << 32) - | (((uint64_t) floatToHalf(1.0f)) << 48); -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp deleted file mode 100644 index e41b645cce..0000000000 --- a/libs/ultrahdr/icc.cpp +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef USE_BIG_ENDIAN -#define USE_BIG_ENDIAN true -#endif - -#include -#include -#include - -#ifndef FLT_MAX -#define FLT_MAX 0x1.fffffep127f -#endif - -namespace android::ultrahdr { -static void Matrix3x3_apply(const Matrix3x3* m, float* x) { - float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2]; - float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2]; - float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2]; - x[0] = y0; - x[1] = y1; - x[2] = y2; -} - -bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) { - double a00 = src->vals[0][0], - a01 = src->vals[1][0], - a02 = src->vals[2][0], - a10 = src->vals[0][1], - a11 = src->vals[1][1], - a12 = src->vals[2][1], - a20 = src->vals[0][2], - a21 = src->vals[1][2], - a22 = src->vals[2][2]; - - double b0 = a00*a11 - a01*a10, - b1 = a00*a12 - a02*a10, - b2 = a01*a12 - a02*a11, - b3 = a20, - b4 = a21, - b5 = a22; - - double determinant = b0*b5 - - b1*b4 - + b2*b3; - - if (determinant == 0) { - return false; - } - - double invdet = 1.0 / determinant; - if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) { - return false; - } - - b0 *= invdet; - b1 *= invdet; - b2 *= invdet; - b3 *= invdet; - b4 *= invdet; - b5 *= invdet; - - dst->vals[0][0] = (float)( a11*b5 - a12*b4 ); - dst->vals[1][0] = (float)( a02*b4 - a01*b5 ); - dst->vals[2][0] = (float)( + b2 ); - dst->vals[0][1] = (float)( a12*b3 - a10*b5 ); - dst->vals[1][1] = (float)( a00*b5 - a02*b3 ); - dst->vals[2][1] = (float)( - b1 ); - dst->vals[0][2] = (float)( a10*b4 - a11*b3 ); - dst->vals[1][2] = (float)( a01*b3 - a00*b4 ); - dst->vals[2][2] = (float)( + b0 ); - - for (int r = 0; r < 3; ++r) - for (int c = 0; c < 3; ++c) { - if (!isfinitef_(dst->vals[r][c])) { - return false; - } - } - return true; -} - -static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) { - Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } }; - for (int r = 0; r < 3; r++) - for (int c = 0; c < 3; c++) { - m.vals[r][c] = A->vals[r][0] * B->vals[0][c] - + A->vals[r][1] * B->vals[1][c] - + A->vals[r][2] * B->vals[2][c]; - } - return m; -} - -static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) { - float v[3] = { - xyz_float[0] / kD50_x, - xyz_float[1] / kD50_y, - xyz_float[2] / kD50_z, - }; - for (size_t i = 0; i < 3; ++i) { - v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f); - } - const float L = v[1] * 116.0f - 16.0f; - const float a = (v[0] - v[1]) * 500.0f; - const float b = (v[1] - v[2]) * 200.0f; - const float Lab_unorm[3] = { - L * (1 / 100.f), - (a + 128.0f) * (1 / 255.0f), - (b + 128.0f) * (1 / 255.0f), - }; - // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the - // table, but the spec appears to indicate that the value should be 0xFF00. - // https://crbug.com/skia/13807 - for (size_t i = 0; i < 3; ++i) { - reinterpret_cast(grid16_lab)[i] = - Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i])); - } -} - -std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut) { - std::string result; - switch (gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - result += "sRGB"; - break; - case ULTRAHDR_COLORGAMUT_P3: - result += "Display P3"; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - result += "Rec2020"; - break; - default: - result += "Unknown"; - break; - } - result += " Gamut with "; - switch (tf) { - case ULTRAHDR_TF_SRGB: - result += "sRGB"; - break; - case ULTRAHDR_TF_LINEAR: - result += "Linear"; - break; - case ULTRAHDR_TF_PQ: - result += "PQ"; - break; - case ULTRAHDR_TF_HLG: - result += "HLG"; - break; - default: - result += "Unknown"; - break; - } - result += " Transfer"; - return result; -} - -sp IccHelper::write_text_tag(const char* text) { - uint32_t text_length = strlen(text); - uint32_t header[] = { - Endian_SwapBE32(kTAG_TextType), // Type signature - 0, // Reserved - Endian_SwapBE32(1), // Number of records - Endian_SwapBE32(12), // Record size (must be 12) - Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')), // English USA - Endian_SwapBE32(2 * text_length), // Length of string in bytes - Endian_SwapBE32(28), // Offset of string - }; - - uint32_t total_length = text_length * 2 + sizeof(header); - total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = sp::make(total_length); - - if (!dataStruct->write(header, sizeof(header))) { - ALOGE("write_text_tag(): error in writing data"); - return dataStruct; - } - - for (size_t i = 0; i < text_length; i++) { - // Convert ASCII to big-endian UTF-16. - dataStruct->write8(0); - dataStruct->write8(text[i]); - } - - return dataStruct; -} - -sp IccHelper::write_xyz_tag(float x, float y, float z) { - uint32_t data[] = { - Endian_SwapBE32(kXYZ_PCSSpace), - 0, - static_cast(Endian_SwapBE32(float_round_to_fixed(x))), - static_cast(Endian_SwapBE32(float_round_to_fixed(y))), - static_cast(Endian_SwapBE32(float_round_to_fixed(z))), - }; - sp dataStruct = sp::make(sizeof(data)); - dataStruct->write(&data, sizeof(data)); - return dataStruct; -} - -sp IccHelper::write_trc_tag(const int table_entries, const void* table_16) { - int total_length = 4 + 4 + 4 + table_entries * 2; - total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = sp::make(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type - dataStruct->write32(0); // Reserved - dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count - for (size_t i = 0; i < table_entries; ++i) { - uint16_t value = reinterpret_cast(table_16)[i]; - dataStruct->write16(value); - } - return dataStruct; -} - -sp IccHelper::write_trc_tag(const TransferFunction& fn) { - if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f - && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) { - int total_length = 16; - sp dataStruct = new DataStruct(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type - dataStruct->write32(0); // Reserved - dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType)); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g))); - return dataStruct; - } - - int total_length = 40; - sp dataStruct = new DataStruct(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type - dataStruct->write32(0); // Reserved - dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType)); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f))); - return dataStruct; -} - -float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) { - if (L <= 0.f) { - return 1.f; - } - if (tf == ULTRAHDR_TF_PQ) { - // The PQ transfer function will map to the range [0, 1]. Linearly scale - // it up to the range [0, 10,000/203]. We will then tone map that back - // down to [0, 1]. - constexpr float kInputMaxLuminance = 10000 / 203.f; - constexpr float kOutputMaxLuminance = 1.0; - L *= kInputMaxLuminance; - - // Compute the tone map gain which will tone map from 10,000/203 to 1.0. - constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance); - constexpr float kToneMapB = 1.f / kOutputMaxLuminance; - return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L); - } - if (tf == ULTRAHDR_TF_HLG) { - // Let Lw be the brightness of the display in nits. - constexpr float Lw = 203.f; - const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f); - return std::pow(L, gamma - 1.f); - } - return 1.f; -} - -sp IccHelper::write_cicp_tag(uint32_t color_primaries, - uint32_t transfer_characteristics) { - int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1 - sp dataStruct = sp::make(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_cicp)); // Type signature - dataStruct->write32(0); // Reserved - dataStruct->write8(color_primaries); // Color primaries - dataStruct->write8(transfer_characteristics); // Transfer characteristics - dataStruct->write8(0); // RGB matrix - dataStruct->write8(1); // Full range - return dataStruct; -} - -void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) { - // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50. - Matrix3x3 src_to_rec2020; - const Matrix3x3 rec2020_to_XYZD50 = kRec2020; - { - Matrix3x3 XYZD50_to_rec2020; - Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020); - src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50); - } - - // Convert the source signal to linear. - for (size_t i = 0; i < kNumChannels; ++i) { - rgb[i] = pqOetf(rgb[i]); - } - - // Convert source gamut to Rec2020. - Matrix3x3_apply(&src_to_rec2020, rgb); - - // Compute the luminance of the signal. - float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}}); - - // Compute the tone map gain based on the luminance. - float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L); - - // Apply the tone map gain. - for (size_t i = 0; i < kNumChannels; ++i) { - rgb[i] *= tone_map_gain; - } - - // Convert from Rec2020-linear to XYZD50. - Matrix3x3_apply(&rec2020_to_XYZD50, rgb); -} - -sp IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* grid_16) { - uint32_t value_count = kNumChannels; - for (uint32_t i = 0; i < kNumChannels; ++i) { - value_count *= grid_points[i]; - } - - int total_length = 20 + 2 * value_count; - total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - sp dataStruct = sp::make(total_length); - - for (size_t i = 0; i < 16; ++i) { - dataStruct->write8(i < kNumChannels ? grid_points[i] : 0); // Grid size - } - dataStruct->write8(2); // Grid byte width (always 16-bit) - dataStruct->write8(0); // Reserved - dataStruct->write8(0); // Reserved - dataStruct->write8(0); // Reserved - - for (uint32_t i = 0; i < value_count; ++i) { - uint16_t value = reinterpret_cast(grid_16)[i]; - dataStruct->write16(value); - } - - return dataStruct; -} - -sp IccHelper::write_mAB_or_mBA_tag(uint32_t type, - bool has_a_curves, - const uint8_t* grid_points, - const uint8_t* grid_16) { - const size_t b_curves_offset = 32; - sp b_curves_data[kNumChannels]; - sp a_curves_data[kNumChannels]; - size_t clut_offset = 0; - sp clut; - size_t a_curves_offset = 0; - - // The "B" curve is required. - for (size_t i = 0; i < kNumChannels; ++i) { - b_curves_data[i] = write_trc_tag(kLinear_TransFun); - } - - // The "A" curve and CLUT are optional. - if (has_a_curves) { - clut_offset = b_curves_offset; - for (size_t i = 0; i < kNumChannels; ++i) { - clut_offset += b_curves_data[i]->getLength(); - } - clut = write_clut(grid_points, grid_16); - - a_curves_offset = clut_offset + clut->getLength(); - for (size_t i = 0; i < kNumChannels; ++i) { - a_curves_data[i] = write_trc_tag(kLinear_TransFun); - } - } - - int total_length = b_curves_offset; - for (size_t i = 0; i < kNumChannels; ++i) { - total_length += b_curves_data[i]->getLength(); - } - if (has_a_curves) { - total_length += clut->getLength(); - for (size_t i = 0; i < kNumChannels; ++i) { - total_length += a_curves_data[i]->getLength(); - } - } - sp dataStruct = sp::make(total_length); - dataStruct->write32(Endian_SwapBE32(type)); // Type signature - dataStruct->write32(0); // Reserved - dataStruct->write8(kNumChannels); // Input channels - dataStruct->write8(kNumChannels); // Output channels - dataStruct->write16(0); // Reserved - dataStruct->write32(Endian_SwapBE32(b_curves_offset)); // B curve offset - dataStruct->write32(Endian_SwapBE32(0)); // Matrix offset (ignored) - dataStruct->write32(Endian_SwapBE32(0)); // M curve offset (ignored) - dataStruct->write32(Endian_SwapBE32(clut_offset)); // CLUT offset - dataStruct->write32(Endian_SwapBE32(a_curves_offset)); // A curve offset - for (size_t i = 0; i < kNumChannels; ++i) { - if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) { - return dataStruct; - } - } - if (has_a_curves) { - dataStruct->write(clut->getData(), clut->getLength()); - for (size_t i = 0; i < kNumChannels; ++i) { - dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength()); - } - } - return dataStruct; -} - -sp IccHelper::writeIccProfile(ultrahdr_transfer_function tf, - ultrahdr_color_gamut gamut) { - ICCHeader header; - - std::vector>> tags; - - // Compute profile description tag - std::string desc = get_desc_string(tf, gamut); - - tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str())); - - Matrix3x3 toXYZD50; - switch (gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - toXYZD50 = kSRGB; - break; - case ULTRAHDR_COLORGAMUT_P3: - toXYZD50 = kDisplayP3; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - toXYZD50 = kRec2020; - break; - default: - // Should not fall here. - return nullptr; - } - - // Compute primaries. - { - tags.emplace_back(kTAG_rXYZ, - write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0])); - tags.emplace_back(kTAG_gXYZ, - write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1])); - tags.emplace_back(kTAG_bXYZ, - write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2])); - } - - // Compute white point tag (must be D50) - tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z)); - - // Compute transfer curves. - if (tf != ULTRAHDR_TF_PQ) { - if (tf == ULTRAHDR_TF_HLG) { - std::vector trc_table; - trc_table.resize(kTrcTableSize * 2); - for (uint32_t i = 0; i < kTrcTableSize; ++i) { - float x = i / (kTrcTableSize - 1.f); - float y = hlgOetf(x); - y *= compute_tone_map_gain(tf, y); - float_to_table16(y, &trc_table[2 * i]); - } - - tags.emplace_back(kTAG_rTRC, - write_trc_tag(kTrcTableSize, reinterpret_cast(trc_table.data()))); - tags.emplace_back(kTAG_gTRC, - write_trc_tag(kTrcTableSize, reinterpret_cast(trc_table.data()))); - tags.emplace_back(kTAG_bTRC, - write_trc_tag(kTrcTableSize, reinterpret_cast(trc_table.data()))); - } else { - tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun)); - tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun)); - tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun)); - } - } - - // Compute CICP. - if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) { - // The CICP tag is present in ICC 4.4, so update the header's version. - header.version = Endian_SwapBE32(0x04400000); - - uint32_t color_primaries = 0; - if (gamut == ULTRAHDR_COLORGAMUT_BT709) { - color_primaries = kCICPPrimariesSRGB; - } else if (gamut == ULTRAHDR_COLORGAMUT_P3) { - color_primaries = kCICPPrimariesP3; - } - - uint32_t transfer_characteristics = 0; - if (tf == ULTRAHDR_TF_SRGB) { - transfer_characteristics = kCICPTrfnSRGB; - } else if (tf == ULTRAHDR_TF_LINEAR) { - transfer_characteristics = kCICPTrfnLinear; - } else if (tf == ULTRAHDR_TF_PQ) { - transfer_characteristics = kCICPTrfnPQ; - } else if (tf == ULTRAHDR_TF_HLG) { - transfer_characteristics = kCICPTrfnHLG; - } - tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics)); - } - - // Compute A2B0. - if (tf == ULTRAHDR_TF_PQ) { - std::vector a2b_grid; - a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2); - size_t a2b_grid_index = 0; - for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) { - for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) { - for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) { - float rgb[3] = { - r_index / (kGridSize - 1.f), - g_index / (kGridSize - 1.f), - b_index / (kGridSize - 1.f), - }; - compute_lut_entry(toXYZD50, rgb); - float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]); - a2b_grid_index += 6; - } - } - } - const uint8_t* grid_16 = reinterpret_cast(a2b_grid.data()); - - uint8_t grid_points[kNumChannels]; - for (size_t i = 0; i < kNumChannels; ++i) { - grid_points[i] = kGridSize; - } - - auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType, - /* has_a_curves */ true, - grid_points, - grid_16); - tags.emplace_back(kTAG_A2B0, std::move(a2b_data)); - } - - // Compute B2A0. - if (tf == ULTRAHDR_TF_PQ) { - auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType, - /* has_a_curves */ false, - /* grid_points */ nullptr, - /* grid_16 */ nullptr); - tags.emplace_back(kTAG_B2A0, std::move(b2a_data)); - } - - // Compute copyright tag - tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022")); - - // Compute the size of the profile. - size_t tag_data_size = 0; - for (const auto& tag : tags) { - tag_data_size += tag.second->getLength(); - } - size_t tag_table_size = kICCTagTableEntrySize * tags.size(); - size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size; - - sp dataStruct = sp::make(profile_size + kICCIdentifierSize); - - // Write identifier, chunk count, and chunk ID - if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) || - !dataStruct->write8(1) || !dataStruct->write8(1)) { - ALOGE("writeIccProfile(): error in identifier"); - return dataStruct; - } - - // Write the header. - header.data_color_space = Endian_SwapBE32(Signature_RGB); - header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ); - header.size = Endian_SwapBE32(profile_size); - header.tag_count = Endian_SwapBE32(tags.size()); - - if (!dataStruct->write(&header, sizeof(header))) { - ALOGE("writeIccProfile(): error in header"); - return dataStruct; - } - - // Write the tag table. Track the offset and size of the previous tag to - // compute each tag's offset. An empty SkData indicates that the previous - // tag is to be reused. - uint32_t last_tag_offset = sizeof(header) + tag_table_size; - uint32_t last_tag_size = 0; - for (const auto& tag : tags) { - last_tag_offset = last_tag_offset + last_tag_size; - last_tag_size = tag.second->getLength(); - uint32_t tag_table_entry[3] = { - Endian_SwapBE32(tag.first), - Endian_SwapBE32(last_tag_offset), - Endian_SwapBE32(last_tag_size), - }; - if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) { - ALOGE("writeIccProfile(): error in writing tag table"); - return dataStruct; - } - } - - // Write the tags. - for (const auto& tag : tags) { - if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) { - ALOGE("writeIccProfile(): error in writing tags"); - return dataStruct; - } - } - - return dataStruct; -} - -bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, - const uint8_t* red_tag, - const uint8_t* green_tag, - const uint8_t* blue_tag) { - sp red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0], - matrix.vals[2][0]); - sp green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1], - matrix.vals[2][1]); - sp blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2], - matrix.vals[2][2]); - return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 && - memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 && - memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0; -} - -ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) { - // Each tag table entry consists of 3 fields of 4 bytes each. - static const size_t kTagTableEntrySize = 12; - - if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } - - if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } - - uint8_t* icc_bytes = reinterpret_cast(icc_data) + kICCIdentifierSize; - - ICCHeader* header = reinterpret_cast(icc_bytes); - - // Use 0 to indicate not found, since offsets are always relative to start - // of ICC data and therefore a tag offset of zero would never be valid. - size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0; - size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0; - for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) { - uint32_t* tag_entry_start = reinterpret_cast( - icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize); - // first 4 bytes are the tag signature, next 4 bytes are the tag offset, - // last 4 bytes are the tag length in bytes. - if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) { - red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); - red_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); - } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) { - green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); - green_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); - } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) { - blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); - blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); - } - } - - if (red_primary_offset == 0 || red_primary_size != kColorantTagSize || - kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size || - green_primary_offset == 0 || green_primary_size != kColorantTagSize || - kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size || - blue_primary_offset == 0 || blue_primary_size != kColorantTagSize || - kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } - - uint8_t* red_tag = icc_bytes + red_primary_offset; - uint8_t* green_tag = icc_bytes + green_primary_offset; - uint8_t* blue_tag = icc_bytes + blue_primary_offset; - - // Serialize tags as we do on encode and compare what we find to that to - // determine the gamut (since we don't have a need yet for full deserialize). - if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_BT709; - } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_P3; - } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_BT2100; - } - - // Didn't find a match to one of the profiles we write; indicate the gamut - // is unspecified since we don't understand it. - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h deleted file mode 100644 index 9f1238f718..0000000000 --- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_RECOVERYMAPMATH_H -#define ANDROID_ULTRAHDR_RECOVERYMAPMATH_H - -#include -#include - -#include - -namespace android::ultrahdr { - -#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x) - -//////////////////////////////////////////////////////////////////////////////// -// Framework - -const float kSdrWhiteNits = 100.0f; -const float kHlgMaxNits = 1000.0f; -const float kPqMaxNits = 10000.0f; - -struct Color { - union { - struct { - float r; - float g; - float b; - }; - struct { - float y; - float u; - float v; - }; - }; -}; - -typedef Color (*ColorTransformFn)(Color); -typedef float (*ColorCalculationFn)(Color); - -// A transfer function mapping encoded values to linear values, -// represented by this 7-parameter piecewise function: -// -// linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d -// = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded| -// -// (A simple gamma transfer function sets g to gamma and a to 1.) -typedef struct TransferFunction { - float g, a,b,c,d,e,f; -} TransferFunction; - -static constexpr TransferFunction kSRGB_TransFun = - { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f }; - -static constexpr TransferFunction kLinear_TransFun = - { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - -inline Color operator+=(Color& lhs, const Color& rhs) { - lhs.r += rhs.r; - lhs.g += rhs.g; - lhs.b += rhs.b; - return lhs; -} -inline Color operator-=(Color& lhs, const Color& rhs) { - lhs.r -= rhs.r; - lhs.g -= rhs.g; - lhs.b -= rhs.b; - return lhs; -} - -inline Color operator+(const Color& lhs, const Color& rhs) { - Color temp = lhs; - return temp += rhs; -} -inline Color operator-(const Color& lhs, const Color& rhs) { - Color temp = lhs; - return temp -= rhs; -} - -inline Color operator+=(Color& lhs, const float rhs) { - lhs.r += rhs; - lhs.g += rhs; - lhs.b += rhs; - return lhs; -} -inline Color operator-=(Color& lhs, const float rhs) { - lhs.r -= rhs; - lhs.g -= rhs; - lhs.b -= rhs; - return lhs; -} -inline Color operator*=(Color& lhs, const float rhs) { - lhs.r *= rhs; - lhs.g *= rhs; - lhs.b *= rhs; - return lhs; -} -inline Color operator/=(Color& lhs, const float rhs) { - lhs.r /= rhs; - lhs.g /= rhs; - lhs.b /= rhs; - return lhs; -} - -inline Color operator+(const Color& lhs, const float rhs) { - Color temp = lhs; - return temp += rhs; -} -inline Color operator-(const Color& lhs, const float rhs) { - Color temp = lhs; - return temp -= rhs; -} -inline Color operator*(const Color& lhs, const float rhs) { - Color temp = lhs; - return temp *= rhs; -} -inline Color operator/(const Color& lhs, const float rhs) { - Color temp = lhs; - return temp /= rhs; -} - -inline uint16_t floatToHalf(float f) { - // round-to-nearest-even: add last bit after truncated mantissa - const uint32_t b = *((uint32_t*)&f) + 0x00001000; - - const uint32_t e = (b & 0x7F800000) >> 23; // exponent - const uint32_t m = b & 0x007FFFFF; // mantissa - - // sign : normalized : denormalized : saturate - return (b & 0x80000000) >> 16 - | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) - | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) - | (e > 143) * 0x7FFF; -} - -constexpr size_t kGainFactorPrecision = 10; -constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision; -struct GainLUT { - GainLUT(ultrahdr_metadata_ptr metadata) { - for (int idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->minContentBoost) * (1.0f - value) - + log2(metadata->maxContentBoost) * value; - mGainTable[idx] = exp2(logBoost); - } - } - - GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) { - float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f; - for (int idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->minContentBoost) * (1.0f - value) - + log2(metadata->maxContentBoost) * value; - mGainTable[idx] = exp2(logBoost * boostFactor); - } - } - - ~GainLUT() { - } - - float getGainFactor(float gain) { - uint32_t idx = static_cast(gain * (kGainFactorNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place - idx = CLIP3(idx, 0, kGainFactorNumEntries - 1); - return mGainTable[idx]; - } - -private: - float mGainTable[kGainFactorNumEntries]; -}; - -struct ShepardsIDW { - ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} { - const int size = mMapScaleFactor * mMapScaleFactor * 4; - mWeights = new float[size]; - mWeightsNR = new float[size]; - mWeightsNB = new float[size]; - mWeightsC = new float[size]; - fillShepardsIDW(mWeights, 1, 1); - fillShepardsIDW(mWeightsNR, 0, 1); - fillShepardsIDW(mWeightsNB, 1, 0); - fillShepardsIDW(mWeightsC, 0, 0); - } - ~ShepardsIDW() { - delete[] mWeights; - delete[] mWeightsNR; - delete[] mWeightsNB; - delete[] mWeightsC; - } - - int mMapScaleFactor; - // Image :- - // p00 p01 p02 p03 p04 p05 p06 p07 - // p10 p11 p12 p13 p14 p15 p16 p17 - // p20 p21 p22 p23 p24 p25 p26 p27 - // p30 p31 p32 p33 p34 p35 p36 p37 - // p40 p41 p42 p43 p44 p45 p46 p47 - // p50 p51 p52 p53 p54 p55 p56 p57 - // p60 p61 p62 p63 p64 p65 p66 p67 - // p70 p71 p72 p73 p74 p75 p76 p77 - - // Gain Map (for 4 scale factor) :- - // m00 p01 - // m10 m11 - - // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during - // reconstruction. hence table weight size is 4. - float* mWeights; - // TODO: check if its ok to mWeights at places - float* mWeightsNR; // no right - float* mWeightsNB; // no bottom - float* mWeightsC; // no right & bottom - - float euclideanDistance(float x1, float x2, float y1, float y2); - void fillShepardsIDW(float *weights, int incR, int incB); -}; - -//////////////////////////////////////////////////////////////////////////////// -// sRGB transformations -// NOTE: sRGB has the same color primaries as BT.709, but different transfer -// function. For this reason, all sRGB transformations here apply to BT.709, -// except for those concerning transfer functions. - -/* - * Calculate the luminance of a linear RGB sRGB pixel, according to - * IEC 61966-2-1/Amd 1:2003. - * - * [0.0, 1.0] range in and out. - */ -float srgbLuminance(Color e); - -/* - * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6. - * - * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace. - */ -Color srgbRgbToYuv(Color e_gamma); - - -/* - * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6. - * - * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace. - */ -Color srgbYuvToRgb(Color e_gamma); - -/* - * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003. - * - * [0.0, 1.0] range in and out. - */ -float srgbInvOetf(float e_gamma); -Color srgbInvOetf(Color e_gamma); -float srgbInvOetfLUT(float e_gamma); -Color srgbInvOetfLUT(Color e_gamma); - -constexpr size_t kSrgbInvOETFPrecision = 10; -constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision; - -//////////////////////////////////////////////////////////////////////////////// -// Display-P3 transformations - -/* - * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1. - * - * [0.0, 1.0] range in and out. - */ -float p3Luminance(Color e); - -/* - * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7. - * - * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace. - */ -Color p3RgbToYuv(Color e_gamma); - -/* - * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7. - * - * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace. - */ -Color p3YuvToRgb(Color e_gamma); - - -//////////////////////////////////////////////////////////////////////////////// -// BT.2100 transformations - according to ITU-R BT.2100-2 - -/* - * Calculate the luminance of a linear RGB BT.2100 pixel. - * - * [0.0, 1.0] range in and out. - */ -float bt2100Luminance(Color e); - -/* - * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2. - * - * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace. - */ -Color bt2100RgbToYuv(Color e_gamma); - -/* - * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2. - * - * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace. - */ -Color bt2100YuvToRgb(Color e_gamma); - -/* - * Convert from scene luminance to HLG. - * - * [0.0, 1.0] range in and out. - */ -float hlgOetf(float e); -Color hlgOetf(Color e); -float hlgOetfLUT(float e); -Color hlgOetfLUT(Color e); - -constexpr size_t kHlgOETFPrecision = 16; -constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision; - -/* - * Convert from HLG to scene luminance. - * - * [0.0, 1.0] range in and out. - */ -float hlgInvOetf(float e_gamma); -Color hlgInvOetf(Color e_gamma); -float hlgInvOetfLUT(float e_gamma); -Color hlgInvOetfLUT(Color e_gamma); - -constexpr size_t kHlgInvOETFPrecision = 12; -constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision; - -/* - * Convert from scene luminance to PQ. - * - * [0.0, 1.0] range in and out. - */ -float pqOetf(float e); -Color pqOetf(Color e); -float pqOetfLUT(float e); -Color pqOetfLUT(Color e); - -constexpr size_t kPqOETFPrecision = 16; -constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision; - -/* - * Convert from PQ to scene luminance in nits. - * - * [0.0, 1.0] range in and out. - */ -float pqInvOetf(float e_gamma); -Color pqInvOetf(Color e_gamma); -float pqInvOetfLUT(float e_gamma); -Color pqInvOetfLUT(Color e_gamma); - -constexpr size_t kPqInvOETFPrecision = 12; -constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision; - - -//////////////////////////////////////////////////////////////////////////////// -// Color space conversions - -/* - * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1. - * - * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the - * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is - * always the inverse of the RGB gamut to XYZ matrix. - */ -Color bt709ToP3(Color e); -Color bt709ToBt2100(Color e); -Color p3ToBt709(Color e); -Color p3ToBt2100(Color e); -Color bt2100ToBt709(Color e); -Color bt2100ToP3(Color e); - -/* - * Identity conversion. - */ -inline Color identityConversion(Color e) { return e; } - -/* - * Get the conversion to apply to the HDR image for gain map generation - */ -ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut); - -/* - * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2. - * - * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is - * treated as Bt.601 by DataSpace, hence we do the same. - */ -Color yuv709To601(Color e_gamma); -Color yuv709To2100(Color e_gamma); -Color yuv601To709(Color e_gamma); -Color yuv601To2100(Color e_gamma); -Color yuv2100To709(Color e_gamma); -Color yuv2100To601(Color e_gamma); - -/* - * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image. - * - * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets - * this result, and UV gets the averaged result. - * - * x_chroma and y_chroma should be less than or equal to half the image's width and height - * respecitively, since input is 4:2:0 subsampled. - */ -void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma, - ColorTransformFn fn); - - -//////////////////////////////////////////////////////////////////////////////// -// Gain map calculations - -/* - * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR - * luminances in linear space, and the hdr ratio to encode against. - * - * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and - * offsetHdr of 0.0, this function doesn't handle different metadata values for - * these fields. - */ -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata); -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, - float log2MinContentBoost, float log2MaxContentBoost); - -/* - * Calculates the linear luminance in nits after applying the given gain - * value, with the given hdr ratio, to the given sdr input in the range [0, 1]. - * - * Note: similar to encodeGain(), this function only supports gamma 1.0, - * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to - * gainMapMax, as this library encodes. - */ -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata); -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost); -Color applyGainLUT(Color e, float gain, GainLUT& gainLUT); - -/* - * Helper for sampling from YUV 420 images. - */ -Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y); - -/* - * Helper for sampling from P010 images. - * - * Expect narrow-range image data for P010. - */ -Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y); - -/* - * Sample the image at the provided location, with a weighting based on nearby - * pixels and the map scale factor. - */ -Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); - -/* - * Sample the image at the provided location, with a weighting based on nearby - * pixels and the map scale factor. - * - * Expect narrow-range image data for P010. - */ -Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); - -/* - * Sample the gain value for the map from a given x,y coordinate on a scale - * that is map scale factor larger than the map size. - */ -float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y); -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, - ShepardsIDW& weightTables); - -/* - * Convert from Color to RGBA1010102. - * - * Alpha always set to 1.0. - */ -uint32_t colorToRgba1010102(Color e_gamma); - -/* - * Convert from Color to F16. - * - * Alpha always set to 1.0. - */ -uint64_t colorToRgbaF16(Color e_gamma); - -} // namespace android::ultrahdr - -#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h deleted file mode 100644 index 971b267fe4..0000000000 --- a/libs/ultrahdr/include/ultrahdr/icc.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_ICC_H -#define ANDROID_ULTRAHDR_ICC_H - -#include -#include -#include -#include -#include -#include - -#ifdef USE_BIG_ENDIAN -#undef USE_BIG_ENDIAN -#define USE_BIG_ENDIAN true -#endif - -namespace android::ultrahdr { - -typedef int32_t Fixed; -#define Fixed1 (1 << 16) -#define MaxS32FitsInFloat 2147483520 -#define MinS32FitsInFloat (-MaxS32FitsInFloat) -#define FixedToFloat(x) ((x) * 1.52587890625e-5f) - -typedef struct Matrix3x3 { - float vals[3][3]; -} Matrix3x3; - -// The D50 illuminant. -constexpr float kD50_x = 0.9642f; -constexpr float kD50_y = 1.0000f; -constexpr float kD50_z = 0.8249f; - -enum { - // data_color_space - Signature_CMYK = 0x434D594B, - Signature_Gray = 0x47524159, - Signature_RGB = 0x52474220, - - // pcs - Signature_Lab = 0x4C616220, - Signature_XYZ = 0x58595A20, -}; - -typedef uint32_t FourByteTag; -static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) { - return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d); -} - -static constexpr char kICCIdentifier[] = "ICC_PROFILE"; -// 12 for the actual identifier, +2 for the chunk count and chunk index which -// will always follow. -static constexpr size_t kICCIdentifierSize = 14; - -// This is equal to the header size according to the ICC specification (128) -// plus the size of the tag count (4). We include the tag count since we -// always require it to be present anyway. -static constexpr size_t kICCHeaderSize = 132; - -// Contains a signature (4), offset (4), and size (4). -static constexpr size_t kICCTagTableEntrySize = 12; - -// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12 -// bytes for a single XYZ number type (4 bytes per coordinate). -static constexpr size_t kColorantTagSize = 20; - -static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r'); -static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' '); -static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' '); -static constexpr uint32_t kACSP_Signature = SetFourByteTag('a', 'c', 's', 'p'); - -static constexpr uint32_t kTAG_desc = SetFourByteTag('d', 'e', 's', 'c'); -static constexpr uint32_t kTAG_TextType = SetFourByteTag('m', 'l', 'u', 'c'); -static constexpr uint32_t kTAG_rXYZ = SetFourByteTag('r', 'X', 'Y', 'Z'); -static constexpr uint32_t kTAG_gXYZ = SetFourByteTag('g', 'X', 'Y', 'Z'); -static constexpr uint32_t kTAG_bXYZ = SetFourByteTag('b', 'X', 'Y', 'Z'); -static constexpr uint32_t kTAG_wtpt = SetFourByteTag('w', 't', 'p', 't'); -static constexpr uint32_t kTAG_rTRC = SetFourByteTag('r', 'T', 'R', 'C'); -static constexpr uint32_t kTAG_gTRC = SetFourByteTag('g', 'T', 'R', 'C'); -static constexpr uint32_t kTAG_bTRC = SetFourByteTag('b', 'T', 'R', 'C'); -static constexpr uint32_t kTAG_cicp = SetFourByteTag('c', 'i', 'c', 'p'); -static constexpr uint32_t kTAG_cprt = SetFourByteTag('c', 'p', 'r', 't'); -static constexpr uint32_t kTAG_A2B0 = SetFourByteTag('A', '2', 'B', '0'); -static constexpr uint32_t kTAG_B2A0 = SetFourByteTag('B', '2', 'A', '0'); - -static constexpr uint32_t kTAG_CurveType = SetFourByteTag('c', 'u', 'r', 'v'); -static constexpr uint32_t kTAG_mABType = SetFourByteTag('m', 'A', 'B', ' '); -static constexpr uint32_t kTAG_mBAType = SetFourByteTag('m', 'B', 'A', ' '); -static constexpr uint32_t kTAG_ParaCurveType = SetFourByteTag('p', 'a', 'r', 'a'); - - -static constexpr Matrix3x3 kSRGB = {{ - // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync. - // 0.436065674f, 0.385147095f, 0.143066406f, - // 0.222488403f, 0.716873169f, 0.060607910f, - // 0.013916016f, 0.097076416f, 0.714096069f, - { FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0) }, - { FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84) }, - { FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF) }, -}}; - -static constexpr Matrix3x3 kDisplayP3 = {{ - { 0.515102f, 0.291965f, 0.157153f }, - { 0.241182f, 0.692236f, 0.0665819f }, - { -0.00104941f, 0.0418818f, 0.784378f }, -}}; - -static constexpr Matrix3x3 kRec2020 = {{ - { 0.673459f, 0.165661f, 0.125100f }, - { 0.279033f, 0.675338f, 0.0456288f }, - { -0.00193139f, 0.0299794f, 0.797162f }, -}}; - -static constexpr uint32_t kCICPPrimariesSRGB = 1; -static constexpr uint32_t kCICPPrimariesP3 = 12; -static constexpr uint32_t kCICPPrimariesRec2020 = 9; - -static constexpr uint32_t kCICPTrfnSRGB = 1; -static constexpr uint32_t kCICPTrfnLinear = 8; -static constexpr uint32_t kCICPTrfnPQ = 16; -static constexpr uint32_t kCICPTrfnHLG = 18; - -enum ParaCurveType { - kExponential_ParaCurveType = 0, - kGAB_ParaCurveType = 1, - kGABC_ParaCurveType = 2, - kGABDE_ParaCurveType = 3, - kGABCDEF_ParaCurveType = 4, -}; - -/** - * Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN. - */ -static inline int float_saturate2int(float x) { - x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat; - x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat; - return (int)x; -} - -static Fixed float_round_to_fixed(float x) { - return float_saturate2int((float)floor((double)x * Fixed1 + 0.5)); -} - -static uint16_t float_round_to_unorm16(float x) { - x = x * 65535.f + 0.5; - if (x > 65535) return 65535; - if (x < 0) return 0; - return static_cast(x); -} - -static void float_to_table16(const float f, uint8_t* table_16) { - *reinterpret_cast(table_16) = Endian_SwapBE16(float_round_to_unorm16(f)); -} - -static bool isfinitef_(float x) { return 0 == x*0; } - -struct ICCHeader { - // Size of the profile (computed) - uint32_t size; - // Preferred CMM type (ignored) - uint32_t cmm_type = 0; - // Version 4.3 or 4.4 if CICP is included. - uint32_t version = Endian_SwapBE32(0x04300000); - // Display device profile - uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile); - // RGB input color space; - uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace); - // Profile connection space. - uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace); - // Date and time (ignored) - uint8_t creation_date_time[12] = {0}; - // Profile signature - uint32_t signature = Endian_SwapBE32(kACSP_Signature); - // Platform target (ignored) - uint32_t platform = 0; - // Flags: not embedded, can be used independently - uint32_t flags = 0x00000000; - // Device manufacturer (ignored) - uint32_t device_manufacturer = 0; - // Device model (ignored) - uint32_t device_model = 0; - // Device attributes (ignored) - uint8_t device_attributes[8] = {0}; - // Relative colorimetric rendering intent - uint32_t rendering_intent = Endian_SwapBE32(1); - // D50 standard illuminant (X, Y, Z) - uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x)); - uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y)); - uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z)); - // Profile creator (ignored) - uint32_t creator = 0; - // Profile id checksum (ignored) - uint8_t profile_id[16] = {0}; - // Reserved (ignored) - uint8_t reserved[28] = {0}; - // Technically not part of header, but required - uint32_t tag_count = 0; -}; - -class IccHelper { -private: - static constexpr uint32_t kTrcTableSize = 65; - static constexpr uint32_t kGridSize = 17; - static constexpr size_t kNumChannels = 3; - - static sp write_text_tag(const char* text); - static std::string get_desc_string(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut); - static sp write_xyz_tag(float x, float y, float z); - static sp write_trc_tag(const int table_entries, const void* table_16); - static sp write_trc_tag(const TransferFunction& fn); - static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L); - static sp write_cicp_tag(uint32_t color_primaries, - uint32_t transfer_characteristics); - static sp write_mAB_or_mBA_tag(uint32_t type, - bool has_a_curves, - const uint8_t* grid_points, - const uint8_t* grid_16); - static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]); - static sp write_clut(const uint8_t* grid_points, const uint8_t* grid_16); - - // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input - // tag buffer assumed to be at least kColorantTagSize in size. - static bool tagsEqualToMatrix(const Matrix3x3& matrix, - const uint8_t* red_tag, - const uint8_t* green_tag, - const uint8_t* blue_tag); - -public: - // Output includes JPEG embedding identifier and chunk information, but not - // APPx information. - static sp writeIccProfile(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut); - // NOTE: this function is not robust; it can infer gamuts that IccHelper - // writes out but should not be considered a reference implementation for - // robust parsing of ICC profiles or their gamuts. - static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size); -}; -} // namespace android::ultrahdr - -#endif //ANDROID_ULTRAHDR_ICC_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h deleted file mode 100644 index b86ce5f450..0000000000 --- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_JPEGDECODERHELPER_H -#define ANDROID_ULTRAHDR_JPEGDECODERHELPER_H - -// We must include cstdio before jpeglib.h. It is a requirement of libjpeg. -#include -extern "C" { -#include -#include -} -#include -#include - -// constraint on max width and max height is only due to device alloc constraints -// Can tune these values basing on the target device -static const int kMaxWidth = 8192; -static const int kMaxHeight = 8192; - -namespace android::ultrahdr { -/* - * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. - * This class is not thread-safe. - */ -class JpegDecoderHelper { -public: - JpegDecoderHelper(); - ~JpegDecoderHelper(); - /* - * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After - * calling this method, call getDecompressedImage() to get the image. - * Returns false if decompressing the image fails. - */ - bool decompressImage(const void* image, int length, bool decodeToRGBA = false); - /* - * Returns the decompressed raw image buffer pointer. This method must be called only after - * calling decompressImage(). - */ - void* getDecompressedImagePtr(); - /* - * Returns the decompressed raw image buffer size. This method must be called only after - * calling decompressImage(). - */ - size_t getDecompressedImageSize(); - /* - * Returns the image width in pixels. This method must be called only after calling - * decompressImage(). - */ - size_t getDecompressedImageWidth(); - /* - * Returns the image width in pixels. This method must be called only after calling - * decompressImage(). - */ - size_t getDecompressedImageHeight(); - /* - * Returns the XMP data from the image. - */ - void* getXMPPtr(); - /* - * Returns the decompressed XMP buffer size. This method must be called only after - * calling decompressImage() or getCompressedImageParameters(). - */ - size_t getXMPSize(); - /* - * Extracts EXIF package and updates the EXIF position / length without decoding the image. - */ - bool extractEXIF(const void* image, int length); - /* - * Returns the EXIF data from the image. - * This method must be called after extractEXIF() or decompressImage(). - */ - void* getEXIFPtr(); - /* - * Returns the decompressed EXIF buffer size. This method must be called only after - * calling decompressImage(), extractEXIF() or getCompressedImageParameters(). - */ - size_t getEXIFSize(); - /* - * Returns the position offset of EXIF package - * (4 bypes offset to FF sign, the byte after FF E1 XX XX ), - * or -1 if no EXIF exists. - * This method must be called after extractEXIF() or decompressImage(). - */ - int getEXIFPos() { return mExifPos; } - /* - * Returns the ICC data from the image. - */ - void* getICCPtr(); - /* - * Returns the decompressed ICC buffer size. This method must be called only after - * calling decompressImage() or getCompressedImageParameters(). - */ - size_t getICCSize(); - /* - * Decompresses metadata of the image. All vectors are owned by the caller. - */ - bool getCompressedImageParameters(const void* image, int length, size_t* pWidth, - size_t* pHeight, std::vector* iccData, - std::vector* exifData); - -private: - bool decode(const void* image, int length, bool decodeToRGBA); - // Returns false if errors occur. - bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel); - bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest); - bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest); - bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest); - // Process 16 lines of Y and 16 lines of U/V each time. - // We must pass at least 16 scanlines according to libjpeg documentation. - static const int kCompressBatchSize = 16; - // The buffer that holds the decompressed result. - std::vector mResultBuffer; - // The buffer that holds XMP Data. - std::vector mXMPBuffer; - // The buffer that holds EXIF Data. - std::vector mEXIFBuffer; - // The buffer that holds ICC Data. - std::vector mICCBuffer; - - // Resolution of the decompressed image. - size_t mWidth; - size_t mHeight; - - // Position of EXIF package, default value is -1 which means no EXIF package appears. - ssize_t mExifPos = -1; -}; -} /* namespace android::ultrahdr */ - -#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h deleted file mode 100644 index 9d06415cb3..0000000000 --- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_JPEGENCODERHELPER_H -#define ANDROID_ULTRAHDR_JPEGENCODERHELPER_H - -// We must include cstdio before jpeglib.h. It is a requirement of libjpeg. -#include -#include - -extern "C" { -#include -#include -} - -#include - -namespace android::ultrahdr { - -#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m)) - -/* - * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. - * This class is not thread-safe. - */ -class JpegEncoderHelper { -public: - JpegEncoderHelper(); - ~JpegEncoderHelper(); - - /* - * Compresses YUV420Planer image to JPEG format. After calling this method, call - * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use. - * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of - * ICC segment which will be added to the compressed image. - * Returns false if errors occur during compression. - */ - bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height, - int lumaStride, int chromaStride, int quality, const void* iccBuffer, - unsigned int iccSize); - - /* - * Returns the compressed JPEG buffer pointer. This method must be called only after calling - * compressImage(). - */ - void* getCompressedImagePtr(); - - /* - * Returns the compressed JPEG buffer size. This method must be called only after calling - * compressImage(). - */ - size_t getCompressedImageSize(); - - /* - * Process 16 lines of Y and 16 lines of U/V each time. - * We must pass at least 16 scanlines according to libjpeg documentation. - */ - static const int kCompressBatchSize = 16; - -private: - // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be - // passed into jpeg library. - static void initDestination(j_compress_ptr cinfo); - static boolean emptyOutputBuffer(j_compress_ptr cinfo); - static void terminateDestination(j_compress_ptr cinfo); - static void outputErrorMessage(j_common_ptr cinfo); - - // Returns false if errors occur. - bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height, - int lumaStride, int chromaStride, int quality, const void* iccBuffer, - unsigned int iccSize); - void setJpegDestination(jpeg_compress_struct* cinfo); - void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo, - bool isSingleChannel); - // Returns false if errors occur. - bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer, - int lumaStride, int chromaStride); - bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride); - - // The block size for encoded jpeg image buffer. - static const int kBlockSize = 16384; - - // The buffer that holds the compressed result. - std::vector mResultBuffer; -}; - -} /* namespace android::ultrahdr */ - -#endif // ANDROID_ULTRAHDR_JPEGENCODERHELPER_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h deleted file mode 100644 index 114c81d818..0000000000 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_JPEGR_H -#define ANDROID_ULTRAHDR_JPEGR_H - -#include -#include - -#include "ultrahdr/jpegdecoderhelper.h" -#include "ultrahdr/jpegencoderhelper.h" -#include "ultrahdr/jpegrerrorcode.h" -#include "ultrahdr/ultrahdr.h" - -#ifndef FLT_MAX -#define FLT_MAX 0x1.fffffep127f -#endif - -namespace android::ultrahdr { - -// The current JPEGR version that we encode to -static const char* const kJpegrVersion = "1.0"; - -// Map is quarter res / sixteenth size -static const size_t kMapDimensionScaleFactor = 4; - -// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to -// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale -// 1 sample is sufficient. We are using 2 here anyways -static const int kMinWidth = 2 * kMapDimensionScaleFactor; -static const int kMinHeight = 2 * kMapDimensionScaleFactor; - -// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter -// JpegEncoderHelper::kCompressBatchSize. -// The width and height of image under compression is expected to be a multiple of MCU size. -// If this criteria is not satisfied, padding is done. -static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize; - -/* - * Holds information of jpegr image - */ -struct jpegr_info_struct { - size_t width; - size_t height; - std::vector* iccData; - std::vector* exifData; -}; - -/* - * Holds information for uncompressed image or gain map. - */ -struct jpegr_uncompressed_struct { - // Pointer to the data location. - void* data; - // Width of the gain map or the luma plane of the image in pixels. - int width; - // Height of the gain map or the luma plane of the image in pixels. - int height; - // Color gamut. - ultrahdr_color_gamut colorGamut; - - // Values below are optional - // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately - // after the luma plane. - void* chroma_data = nullptr; - // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If - // non-zero this value must be larger than or equal to luma width. If stride is - // uninitialized then it is assumed to be equal to luma width. - int luma_stride = 0; - // Stride of UV plane in number of pixels. - // 1. If this handle points to P010 image then this value must be larger than - // or equal to luma width. - // 2. If this handle points to 420 image then this value must be larger than - // or equal to (luma width / 2). - // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way, - // chroma_data is derived from luma ptr, chroma stride is derived from luma stride. - int chroma_stride = 0; -}; - -/* - * Holds information for compressed image or gain map. - */ -struct jpegr_compressed_struct { - // Pointer to the data location. - void* data; - // Used data length in bytes. - int length; - // Maximum available data length in bytes. - int maxLength; - // Color gamut. - ultrahdr_color_gamut colorGamut; -}; - -/* - * Holds information for EXIF metadata. - */ -struct jpegr_exif_struct { - // Pointer to the data location. - void* data; - // Data length; - int length; -}; - -typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; -typedef struct jpegr_compressed_struct* jr_compressed_ptr; -typedef struct jpegr_exif_struct* jr_exif_ptr; -typedef struct jpegr_info_struct* jr_info_ptr; - -class JpegR { -public: - /* - * Experimental only - * - * Encode API-0 - * Compress JPEGR image from 10-bit HDR YUV. - * - * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images, - * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed - * JPEG. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the destination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @param exif pointer to the exif metadata. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest, int quality, jr_exif_ptr exif); - - /* - * Encode API-1 - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. - * - * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append - * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same - * resolution. SDR input is assumed to use the sRGB transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @param exif pointer to the exif metadata. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, - jr_exif_ptr exif); - - /* - * Encode API-2 - * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. - * - * This method requires HAL Hardware JPEG encoder. - * - * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the - * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and - * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB - * transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * Note: the compressed SDR image must be the compressed - * yuv420_image_ptr image in JPEG format. - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, - jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest); - - /* - * Encode API-3 - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. - * - * This method requires HAL Hardware JPEG encoder. - * - * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input - * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an - * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same - * resolution. JPEG image is assumed to use the sRGB transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); - - /* - * Encode API-4 - * Assemble JPEGR image from SDR JPEG and gainmap JPEG. - * - * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC - * profile if one isn't present in the input JPEG image. - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param gainmapjpg_image_ptr gain map image compressed in jpeg format - * @param metadata metadata to be written in XMP of the primary jpeg - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, - jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, - jr_compressed_ptr dest); - - /* - * Decode API - * Decompress JPEGR image. - * - * This method assumes that the JPEGR image contains an ICC profile with primaries that match - * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also - * assumes the base image uses the sRGB transfer function. - * - * This method only supports single gain map metadata values for fields that allow multi-channel - * metadata values. - * @param jpegr_image_ptr compressed JPEGR image. - * @param dest destination of the uncompressed JPEGR image. - * @param max_display_boost (optional) the maximum available boost supported by a display, - * the value must be greater than or equal to 1.0. - * @param exif destination of the decoded EXIF metadata. The default value is NULL where the - decoder will do nothing about it. If configured not NULL the decoder will write - EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} - * @param output_format flag for setting output color format. Its value configures the output - color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}. - ---------------------------------------------------------------------- - | output_format | decoded color format to be written | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_SDR | RGBA_8888 | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_LINEAR | (default)RGBA_F16 linear | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_PQ | RGBA_1010102 PQ | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG | - ---------------------------------------------------------------------- - * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL - where the decoder will do nothing about it. If configured not NULL - the decoder will write the decoded gain_map data into this - structure. The format is defined in - {@code jpegr_uncompressed_struct}. - * @param metadata destination of the decoded metadata. The default value is NULL where the - decoder will do nothing about it. If configured not NULL the decoder will - write metadata into this structure. the format of metadata is defined in - {@code ultrahdr_metadata_struct}. - * @return NO_ERROR if decoding succeeds, error code if error occurs. - */ - status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, - float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, - ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR, - jr_uncompressed_ptr gainmap_image_ptr = nullptr, - ultrahdr_metadata_ptr metadata = nullptr); - - /* - * Gets Info from JPEGR file without decoding it. - * - * This method only supports single gain map metadata values for fields that allow multi-channel - * metadata values. - * - * The output is filled jpegr_info structure - * @param jpegr_image_ptr compressed JPEGR image - * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info - * are owned by the caller - * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise - */ - status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr); - -protected: - /* - * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and - * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images - * must be the same resolution. The SDR input is assumed to use the sRGB transfer function. - * - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param hdr_tf transfer function of the HDR image - * @param metadata everything but "version" is filled in this struct - * @param dest location at which gain map image is stored (caller responsible for memory - of data). - * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, - ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest, - bool sdr_is_601 = false); - - /* - * This method is called in the decoding pipeline. It will take the uncompressed (decoded) - * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as - * input, and calculate the 10-bit recovered image. The recovered output image is the same - * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. - * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to - * be a decoded JPEG for the purpose of YUV interpration. - * - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param gainmap_image_ptr pointer to uncompressed gain map image struct. - * @param metadata JPEG/R metadata extracted from XMP. - * @param output_format flag for setting output color format. if set to - * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image - * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. - * @param max_display_boost the maximum available boost supported by a display - * @param dest reconstructed HDR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata, - ultrahdr_output_format output_format, float max_display_boost, - jr_uncompressed_ptr dest); - -private: - /* - * This method is called in the encoding pipeline. It will encode the gain map. - * - * @param gainmap_image_ptr pointer to uncompressed gain map image struct - * @param jpeg_enc_obj_ptr helper resource to compress gain map - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, - JpegEncoderHelper* jpeg_enc_obj_ptr); - - /* - * This method is called to separate primary image and gain map image from JPEGR - * - * @param jpegr_image_ptr pointer to compressed JPEGR image. - * @param primary_jpg_image_ptr destination of primary image - * @param gainmap_jpg_image_ptr destination of compressed gain map image - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, - jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr); - - /* - * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image, - * the compressed gain map and optionally the exif package as inputs, and generate the XMP - * metadata, and finally append everything in the order of: - * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map - * - * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following - * conditions is fulfilled: - * (1) EXIF package is available from outside input. I.e. pExif != nullptr. - * (2) Input JPEG has EXIF. - * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE - * - * @param primary_jpg_image_ptr destination of primary image - * @param gainmap_jpg_image_ptr destination of compressed gain map image - * @param (nullable) pExif EXIF package - * @param (nullable) pIcc ICC package - * @param icc_size length in bytes of ICC package - * @param metadata JPEG/R metadata to encode in XMP of the jpeg - * @param dest compressed JPEGR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc, - size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); - - /* - * This method will tone map a HDR image to an SDR image. - * - * @param src pointer to uncompressed HDR image struct. HDR image is expected to be - * in p010 color format - * @param dest pointer to store tonemapped SDR image - */ - status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest); - - /* - * This method will convert a YUV420 image from one YUV encoding to another in-place (eg. - * Bt.709 to Bt.601 YUV encoding). - * - * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that - * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data. - * - * @param image the YUV420 image to convert - * @param src_encoding input YUV encoding - * @param dest_encoding output YUV encoding - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dest_encoding); - - /* - * This method will check the validity of the input arguments. - * - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to - * be in 420p color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if the input args are valid, error code is not valid. - */ - status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr); - - /* - * This method will check the validity of the input arguments. - * - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to - * be in 420p color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the destination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @return NO_ERROR if the input args are valid, error code is not valid. - */ - status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, - int quality); -}; -} // namespace android::ultrahdr - -#endif // ANDROID_ULTRAHDR_JPEGR_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h deleted file mode 100644 index 5420e1c9cf..0000000000 --- a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_JPEGRERRORCODE_H -#define ANDROID_ULTRAHDR_JPEGRERRORCODE_H - -#include - -namespace android::ultrahdr { - -enum { - // status_t map for errors in the media framework - // OK or NO_ERROR or 0 represents no error. - - // See system/core/include/utils/Errors.h - // System standard errors from -1 through (possibly) -133 - // - // Errors with special meanings and side effects. - // INVALID_OPERATION: Operation attempted in an illegal state (will try to signal to app). - // DEAD_OBJECT: Signal from CodecBase to MediaCodec that MediaServer has died. - // NAME_NOT_FOUND: Signal from CodecBase to MediaCodec that the component was not found. - - // JPEGR errors - JPEGR_IO_ERROR_BASE = -10000, - ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE, - ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1, - ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2, - ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3, - ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4, - ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 5, - ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 6, - ERROR_JPEGR_INVALID_METADATA = JPEGR_IO_ERROR_BASE - 7, - ERROR_JPEGR_UNSUPPORTED_METADATA = JPEGR_IO_ERROR_BASE - 8, - ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_IO_ERROR_BASE - 9, - - JPEGR_RUNTIME_ERROR_BASE = -20000, - ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, - ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2, - ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3, - ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, - ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5, - - ERROR_JPEGR_UNSUPPORTED_FEATURE = -20000, -}; - -} // namespace android::ultrahdr - -#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H diff --git a/libs/ultrahdr/include/ultrahdr/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h deleted file mode 100644 index 4ab664e798..0000000000 --- a/libs/ultrahdr/include/ultrahdr/jpegrutils.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_JPEGRUTILS_H -#define ANDROID_ULTRAHDR_JPEGRUTILS_H - -#include -#include - -#include -#include -#include -#include - -namespace android::ultrahdr { - -static constexpr uint32_t EndianSwap32(uint32_t value) { - return ((value & 0xFF) << 24) | - ((value & 0xFF00) << 8) | - ((value & 0xFF0000) >> 8) | - (value >> 24); -} -static inline uint16_t EndianSwap16(uint16_t value) { - return static_cast((value >> 8) | ((value & 0xFF) << 8)); -} - -#if USE_BIG_ENDIAN - #define Endian_SwapBE32(n) EndianSwap32(n) - #define Endian_SwapBE16(n) EndianSwap16(n) -#else - #define Endian_SwapBE32(n) (n) - #define Endian_SwapBE16(n) (n) -#endif - -struct ultrahdr_metadata_struct; -/* - * Mutable data structure. Holds information for metadata. - */ -class DataStruct : public RefBase { -private: - void* data; - int writePos; - int length; - ~DataStruct(); - -public: - DataStruct(int s); - void* getData(); - int getLength(); - int getBytesWritten(); - bool write8(uint8_t value); - bool write16(uint16_t value); - bool write32(uint32_t value); - bool write(const void* src, int size); -}; - -/* - * Helper function used for writing data to destination. - * - * @param destination destination of the data to be written. - * @param source source of data being written. - * @param length length of the data to be written. - * @param position cursor in desitination where the data is to be written. - * @return status of succeed or error code. - */ -status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position); - - -/* - * Parses XMP packet and fills metadata with data from XMP - * - * @param xmp_data pointer to XMP packet - * @param xmp_size size of XMP packet - * @param metadata place to store HDR metadata values - * @return true if metadata is successfully retrieved, false otherwise -*/ -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata); - -/* - * This method generates XMP metadata for the primary image. - * - * below is an example of the XMP metadata that this function generates where - * secondary_image_length = 1000 - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * @param secondary_image_length length of secondary image - * @return XMP metadata in type of string - */ -std::string generateXmpForPrimaryImage(int secondary_image_length, - ultrahdr_metadata_struct& metadata); - -/* - * This method generates XMP metadata for the recovery map image. - * - * below is an example of the XMP metadata that this function generates where - * max_content_boost = 8.0 - * min_content_boost = 0.5 - * - * - * - * - * - * - * - * @param metadata JPEG/R metadata to encode as XMP - * @return XMP metadata in type of string - */ - std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata); -} // namespace android::ultrahdr - -#endif //ANDROID_ULTRAHDR_JPEGRUTILS_H diff --git a/libs/ultrahdr/include/ultrahdr/multipictureformat.h b/libs/ultrahdr/include/ultrahdr/multipictureformat.h deleted file mode 100644 index c5bd09d6f2..0000000000 --- a/libs/ultrahdr/include/ultrahdr/multipictureformat.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#ifndef ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H -#define ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H - -#include - -#ifdef USE_BIG_ENDIAN -#undef USE_BIG_ENDIAN -#define USE_BIG_ENDIAN true -#endif - -namespace android::ultrahdr { - -constexpr size_t kNumPictures = 2; -constexpr size_t kMpEndianSize = 4; -constexpr uint16_t kTagSerializedCount = 3; -constexpr uint32_t kTagSize = 12; - -constexpr uint16_t kTypeLong = 0x4; -constexpr uint16_t kTypeUndefined = 0x7; - -static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'}; -constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00}; -constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A}; - -constexpr uint16_t kVersionTag = 0xB000; -constexpr uint16_t kVersionType = kTypeUndefined; -constexpr uint32_t kVersionCount = 4; -constexpr size_t kVersionSize = 4; -constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'}; - -constexpr uint16_t kNumberOfImagesTag = 0xB001; -constexpr uint16_t kNumberOfImagesType = kTypeLong; -constexpr uint32_t kNumberOfImagesCount = 1; - -constexpr uint16_t kMPEntryTag = 0xB002; -constexpr uint16_t kMPEntryType = kTypeUndefined; -constexpr uint32_t kMPEntrySize = 16; - -constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000; -constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000; - -size_t calculateMpfSize(); -sp generateMpf(int primary_image_size, int primary_image_offset, - int secondary_image_size, int secondary_image_offset); - -} // namespace android::ultrahdr - -#endif //ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h deleted file mode 100644 index 0252391a86..0000000000 --- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H -#define ANDROID_ULTRAHDR_ULTRAHDR_H - -#include - -namespace android::ultrahdr { -// Color gamuts for image data -typedef enum { - ULTRAHDR_COLORGAMUT_UNSPECIFIED = -1, - ULTRAHDR_COLORGAMUT_BT709, - ULTRAHDR_COLORGAMUT_P3, - ULTRAHDR_COLORGAMUT_BT2100, - ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100, -} ultrahdr_color_gamut; - -// Transfer functions for image data -// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it. -typedef enum { - ULTRAHDR_TF_UNSPECIFIED = -1, - ULTRAHDR_TF_LINEAR = 0, - ULTRAHDR_TF_HLG = 1, - ULTRAHDR_TF_PQ = 2, - ULTRAHDR_TF_SRGB = 3, - ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB, -} ultrahdr_transfer_function; - -// Target output formats for decoder -typedef enum { - ULTRAHDR_OUTPUT_UNSPECIFIED = -1, - ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format - ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) - ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) - ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) - ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG, -} ultrahdr_output_format; - -/* - * Holds information for gain map related metadata. - * - * Not: all values stored in linear. This differs from the metadata encoding in XMP, where - * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and - * hdrCapacityMax are stored in log2 space. - */ -struct ultrahdr_metadata_struct { - // Ultra HDR format version - std::string version; - // Max Content Boost for the map - float maxContentBoost; - // Min Content Boost for the map - float minContentBoost; - // Gamma of the map data - float gamma; - // Offset for SDR data in map calculations - float offsetSdr; - // Offset for HDR data in map calculations - float offsetHdr; - // HDR capacity to apply the map at all - float hdrCapacityMin; - // HDR capacity to apply the map completely - float hdrCapacityMax; -}; -typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr; - -} // namespace android::ultrahdr - -#endif //ANDROID_ULTRAHDR_ULTRAHDR_H diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp deleted file mode 100644 index 2e7940cc2c..0000000000 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ /dev/null @@ -1,543 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include - -#include - -#include -#include -#include - -using namespace std; - -namespace android::ultrahdr { - -#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m)) - -const uint32_t kAPP0Marker = JPEG_APP0; // JFIF -const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP -const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC - -constexpr uint32_t kICCMarkerHeaderSize = 14; -constexpr uint8_t kICCSig[] = { - 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0', -}; -constexpr uint8_t kXmpNameSpace[] = { - 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e', - '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0', -}; -constexpr uint8_t kExifIdCode[] = { - 'E', 'x', 'i', 'f', '\0', '\0', -}; - -struct jpegr_source_mgr : jpeg_source_mgr { - jpegr_source_mgr(const uint8_t* ptr, int len); - ~jpegr_source_mgr(); - - const uint8_t* mBufferPtr; - size_t mBufferLength; -}; - -struct jpegrerror_mgr { - struct jpeg_error_mgr pub; - jmp_buf setjmp_buffer; -}; - -static void jpegr_init_source(j_decompress_ptr cinfo) { - jpegr_source_mgr* src = static_cast(cinfo->src); - src->next_input_byte = static_cast(src->mBufferPtr); - src->bytes_in_buffer = src->mBufferLength; -} - -static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) { - ALOGE("%s : should not get here", __func__); - return FALSE; -} - -static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { - jpegr_source_mgr* src = static_cast(cinfo->src); - - if (num_bytes > static_cast(src->bytes_in_buffer)) { - ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer"); - } else { - src->next_input_byte += num_bytes; - src->bytes_in_buffer -= num_bytes; - } -} - -static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {} - -jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) - : mBufferPtr(ptr), mBufferLength(len) { - init_source = jpegr_init_source; - fill_input_buffer = jpegr_fill_input_buffer; - skip_input_data = jpegr_skip_input_data; - resync_to_restart = jpeg_resync_to_restart; - term_source = jpegr_term_source; -} - -jpegr_source_mgr::~jpegr_source_mgr() {} - -static void jpegrerror_exit(j_common_ptr cinfo) { - jpegrerror_mgr* err = reinterpret_cast(cinfo->err); - longjmp(err->setjmp_buffer, 1); -} - -JpegDecoderHelper::JpegDecoderHelper() {} - -JpegDecoderHelper::~JpegDecoderHelper() {} - -bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) { - if (image == nullptr || length <= 0) { - ALOGE("Image size can not be handled: %d", length); - return false; - } - mResultBuffer.clear(); - mXMPBuffer.clear(); - return decode(image, length, decodeToRGBA); -} - -void* JpegDecoderHelper::getDecompressedImagePtr() { - return mResultBuffer.data(); -} - -size_t JpegDecoderHelper::getDecompressedImageSize() { - return mResultBuffer.size(); -} - -void* JpegDecoderHelper::getXMPPtr() { - return mXMPBuffer.data(); -} - -size_t JpegDecoderHelper::getXMPSize() { - return mXMPBuffer.size(); -} - -void* JpegDecoderHelper::getEXIFPtr() { - return mEXIFBuffer.data(); -} - -size_t JpegDecoderHelper::getEXIFSize() { - return mEXIFBuffer.size(); -} - -void* JpegDecoderHelper::getICCPtr() { - return mICCBuffer.data(); -} - -size_t JpegDecoderHelper::getICCSize() { - return mICCBuffer.size(); -} - -size_t JpegDecoderHelper::getDecompressedImageWidth() { - return mWidth; -} - -size_t JpegDecoderHelper::getDecompressedImageHeight() { - return mHeight; -} - -// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first -// in the image file. -// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), -// two bytes of package length which is stored in marker->original_length, and the real data -// which is stored in marker->data. -bool JpegDecoderHelper::extractEXIF(const void* image, int length) { - jpeg_decompress_struct cinfo; - jpegr_source_mgr mgr(static_cast(image), length); - jpegrerror_mgr myerr; - - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - - cinfo.src = &mgr; - jpeg_read_header(&cinfo, TRUE); - - size_t pos = 2; // position after SOI - for (jpeg_marker_struct* marker = cinfo.marker_list; - marker; - marker = marker->next) { - - pos += 4; - pos += marker->original_length; - - if (marker->marker != kAPP1Marker) { - continue; - } - - const unsigned int len = marker->data_length; - - if (len > sizeof(kExifIdCode) && - !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { - mEXIFBuffer.resize(len, 0); - memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); - mExifPos = pos - marker->original_length; - break; - } - } - - jpeg_destroy_decompress(&cinfo); - return true; -} - -bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) { - bool status = true; - jpeg_decompress_struct cinfo; - jpegrerror_mgr myerr; - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); - - jpegr_source_mgr mgr(static_cast(image), length); - cinfo.src = &mgr; - if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - // Save XMP data, EXIF data, and ICC data. - // Here we only handle the first XMP / EXIF / ICC package. - // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), - // two bytes of package length which is stored in marker->original_length, and the real data - // which is stored in marker->data. - bool exifAppears = false; - bool xmpAppears = false; - bool iccAppears = false; - size_t pos = 2; // position after SOI - for (jpeg_marker_struct* marker = cinfo.marker_list; - marker && !(exifAppears && xmpAppears && iccAppears); - marker = marker->next) { - pos += 4; - pos += marker->original_length; - if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) { - continue; - } - const unsigned int len = marker->data_length; - if (!xmpAppears && - len > sizeof(kXmpNameSpace) && - !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) { - mXMPBuffer.resize(len+1, 0); - memcpy(static_cast(mXMPBuffer.data()), marker->data, len); - xmpAppears = true; - } else if (!exifAppears && - len > sizeof(kExifIdCode) && - !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { - mEXIFBuffer.resize(len, 0); - memcpy(static_cast(mEXIFBuffer.data()), marker->data, len); - exifAppears = true; - mExifPos = pos - marker->original_length; - } else if (!iccAppears && - len > sizeof(kICCSig) && - !memcmp(marker->data, kICCSig, sizeof(kICCSig))) { - mICCBuffer.resize(len, 0); - memcpy(static_cast(mICCBuffer.data()), marker->data, len); - iccAppears = true; - } - } - - mWidth = cinfo.image_width; - mHeight = cinfo.image_height; - if (mWidth > kMaxWidth || mHeight > kMaxHeight) { - status = false; - goto CleanUp; - } - - if (decodeToRGBA) { - // The primary image is expected to be yuv420 sampling - if (cinfo.jpeg_color_space != JCS_YCbCr) { - status = false; - ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__); - goto CleanUp; - } - if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || - cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || - cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - status = false; - ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__); - goto CleanUp; - } - // 4 bytes per pixel - mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); - cinfo.out_color_space = JCS_EXT_RGBA; - } else { - if (cinfo.jpeg_color_space == JCS_YCbCr) { - if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || - cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || - cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - status = false; - ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__); - goto CleanUp; - } - mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); - } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { - mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0); - } else { - status = false; - ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__); - goto CleanUp; - } - cinfo.out_color_space = cinfo.jpeg_color_space; - cinfo.raw_data_out = TRUE; - } - - cinfo.dct_method = JDCT_ISLOW; - jpeg_start_decompress(&cinfo); - if (!decompress(&cinfo, static_cast(mResultBuffer.data()), - cinfo.jpeg_color_space == JCS_GRAYSCALE)) { - status = false; - goto CleanUp; - } - -CleanUp: - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - - return status; -} - -bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, - bool isSingleChannel) { - return isSingleChannel - ? decompressSingleChannel(cinfo, dest) - : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest) - : decompressYUV(cinfo, dest)); -} - -bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth, - size_t* pHeight, std::vector* iccData, - std::vector* exifData) { - jpeg_decompress_struct cinfo; - jpegrerror_mgr myerr; - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); - - jpegr_source_mgr mgr(static_cast(image), length); - cinfo.src = &mgr; - if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - if (pWidth != nullptr) { - *pWidth = cinfo.image_width; - } - if (pHeight != nullptr) { - *pHeight = cinfo.image_height; - } - - if (iccData != nullptr) { - for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) { - if (marker->marker != kAPP2Marker) { - continue; - } - if (marker->data_length <= kICCMarkerHeaderSize || - memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) { - continue; - } - - iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length); - } - } - - if (exifData != nullptr) { - bool exifAppears = false; - for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears; - marker = marker->next) { - if (marker->marker != kAPP1Marker) { - continue; - } - - const unsigned int len = marker->data_length; - if (len >= sizeof(kExifIdCode) && - !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { - exifData->resize(len, 0); - memcpy(static_cast(exifData->data()), marker->data, len); - exifAppears = true; - } - } - } - - jpeg_destroy_decompress(&cinfo); - return true; -} - -bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) { - JSAMPLE* out = (JSAMPLE*)dest; - - while (cinfo->output_scanline < cinfo->image_height) { - if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false; - out += cinfo->image_width * 4; - } - return true; -} - -bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { - JSAMPROW y[kCompressBatchSize]; - JSAMPROW cb[kCompressBatchSize / 2]; - JSAMPROW cr[kCompressBatchSize / 2]; - JSAMPARRAY planes[3]{y, cb, cr}; - - size_t y_plane_size = cinfo->image_width * cinfo->image_height; - size_t uv_plane_size = y_plane_size / 4; - uint8_t* y_plane = const_cast(dest); - uint8_t* u_plane = const_cast(dest + y_plane_size); - uint8_t* v_plane = const_cast(dest + y_plane_size + uv_plane_size); - std::unique_ptr empty = std::make_unique(cinfo->image_width); - memset(empty.get(), 0, cinfo->image_width); - - const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - bool is_width_aligned = (aligned_width == cinfo->image_width); - std::unique_ptr buffer_intrm = nullptr; - uint8_t* y_plane_intrm = nullptr; - uint8_t* u_plane_intrm = nullptr; - uint8_t* v_plane_intrm = nullptr; - JSAMPROW y_intrm[kCompressBatchSize]; - JSAMPROW cb_intrm[kCompressBatchSize / 2]; - JSAMPROW cr_intrm[kCompressBatchSize / 2]; - JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm}; - if (!is_width_aligned) { - size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; - buffer_intrm = std::make_unique(mcu_row_size); - y_plane_intrm = buffer_intrm.get(); - u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); - v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; - for (int i = 0; i < kCompressBatchSize; ++i) { - y_intrm[i] = y_plane_intrm + i * aligned_width; - } - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - int offset_intrm = i * (aligned_width / 2); - cb_intrm[i] = u_plane_intrm + offset_intrm; - cr_intrm[i] = v_plane_intrm + offset_intrm; - } - } - - while (cinfo->output_scanline < cinfo->image_height) { - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->output_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * cinfo->image_width; - } else { - y[i] = empty.get(); - } - } - // cb, cr only have half scanlines - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - size_t scanline = cinfo->output_scanline / 2 + i; - if (scanline < cinfo->image_height / 2) { - int offset = scanline * (cinfo->image_width / 2); - cb[i] = u_plane + offset; - cr[i] = v_plane + offset; - } else { - cb[i] = cr[i] = empty.get(); - } - } - - int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, - kCompressBatchSize); - if (processed != kCompressBatchSize) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - if (!is_width_aligned) { - for (int i = 0; i < kCompressBatchSize; ++i) { - memcpy(y[i], y_intrm[i], cinfo->image_width); - } - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2); - memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2); - } - } - } - return true; -} - -bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, - const uint8_t* dest) { - JSAMPROW y[kCompressBatchSize]; - JSAMPARRAY planes[1]{y}; - - uint8_t* y_plane = const_cast(dest); - std::unique_ptr empty = std::make_unique(cinfo->image_width); - memset(empty.get(), 0, cinfo->image_width); - - int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - bool is_width_aligned = (aligned_width == cinfo->image_width); - std::unique_ptr buffer_intrm = nullptr; - uint8_t* y_plane_intrm = nullptr; - JSAMPROW y_intrm[kCompressBatchSize]; - JSAMPARRAY planes_intrm[1]{y_intrm}; - if (!is_width_aligned) { - size_t mcu_row_size = aligned_width * kCompressBatchSize; - buffer_intrm = std::make_unique(mcu_row_size); - y_plane_intrm = buffer_intrm.get(); - for (int i = 0; i < kCompressBatchSize; ++i) { - y_intrm[i] = y_plane_intrm + i * aligned_width; - } - } - - while (cinfo->output_scanline < cinfo->image_height) { - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->output_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * cinfo->image_width; - } else { - y[i] = empty.get(); - } - } - - int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, - kCompressBatchSize); - if (processed != kCompressBatchSize / 2) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - if (!is_width_aligned) { - for (int i = 0; i < kCompressBatchSize; ++i) { - memcpy(y[i], y_intrm[i], cinfo->image_width); - } - } - } - return true; -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp deleted file mode 100644 index 13ae7424d5..0000000000 --- a/libs/ultrahdr/jpegencoderhelper.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include - -#include -#include - -namespace android::ultrahdr { - -// The destination manager that can access |mResultBuffer| in JpegEncoderHelper. -struct destination_mgr { - struct jpeg_destination_mgr mgr; - JpegEncoderHelper* encoder; -}; - -JpegEncoderHelper::JpegEncoderHelper() {} - -JpegEncoderHelper::~JpegEncoderHelper() {} - -bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, - int height, int lumaStride, int chromaStride, int quality, - const void* iccBuffer, unsigned int iccSize) { - mResultBuffer.clear(); - if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer, - iccSize)) { - return false; - } - ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height, - mResultBuffer.size()); - return true; -} - -void* JpegEncoderHelper::getCompressedImagePtr() { - return mResultBuffer.data(); -} - -size_t JpegEncoderHelper::getCompressedImageSize() { - return mResultBuffer.size(); -} - -void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) { - destination_mgr* dest = reinterpret_cast(cinfo->dest); - std::vector& buffer = dest->encoder->mResultBuffer; - buffer.resize(kBlockSize); - dest->mgr.next_output_byte = &buffer[0]; - dest->mgr.free_in_buffer = buffer.size(); -} - -boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) { - destination_mgr* dest = reinterpret_cast(cinfo->dest); - std::vector& buffer = dest->encoder->mResultBuffer; - size_t oldsize = buffer.size(); - buffer.resize(oldsize + kBlockSize); - dest->mgr.next_output_byte = &buffer[oldsize]; - dest->mgr.free_in_buffer = kBlockSize; - return true; -} - -void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) { - destination_mgr* dest = reinterpret_cast(cinfo->dest); - std::vector& buffer = dest->encoder->mResultBuffer; - buffer.resize(buffer.size() - dest->mgr.free_in_buffer); -} - -void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) { - char buffer[JMSG_LENGTH_MAX]; - - /* Create the message */ - (*cinfo->err->format_message)(cinfo, buffer); - ALOGE("%s\n", buffer); -} - -bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, - int height, int lumaStride, int chromaStride, int quality, - const void* iccBuffer, unsigned int iccSize) { - jpeg_compress_struct cinfo; - jpeg_error_mgr jerr; - - cinfo.err = jpeg_std_error(&jerr); - cinfo.err->output_message = &outputErrorMessage; - jpeg_create_compress(&cinfo); - setJpegDestination(&cinfo); - setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr); - jpeg_start_compress(&cinfo, TRUE); - if (iccBuffer != nullptr && iccSize > 0) { - jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast(iccBuffer), iccSize); - } - bool status = cinfo.num_components == 1 - ? compressY(&cinfo, yBuffer, lumaStride) - : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride); - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - - return status; -} - -void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { - destination_mgr* dest = static_cast( - (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, - sizeof(destination_mgr))); - dest->encoder = this; - dest->mgr.init_destination = &initDestination; - dest->mgr.empty_output_buffer = &emptyOutputBuffer; - dest->mgr.term_destination = &terminateDestination; - cinfo->dest = reinterpret_cast(dest); -} - -void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality, - jpeg_compress_struct* cinfo, bool isSingleChannel) { - cinfo->image_width = width; - cinfo->image_height = height; - cinfo->input_components = isSingleChannel ? 1 : 3; - cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr; - jpeg_set_defaults(cinfo); - jpeg_set_quality(cinfo, quality, TRUE); - cinfo->raw_data_in = TRUE; - cinfo->dct_method = JDCT_ISLOW; - cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2; - cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2; - for (int i = 1; i < cinfo->num_components; i++) { - cinfo->comp_info[i].h_samp_factor = 1; - cinfo->comp_info[i].v_samp_factor = 1; - } -} - -bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, - const uint8_t* uvBuffer, int lumaStride, int chromaStride) { - JSAMPROW y[kCompressBatchSize]; - JSAMPROW cb[kCompressBatchSize / 2]; - JSAMPROW cr[kCompressBatchSize / 2]; - JSAMPARRAY planes[3]{y, cb, cr}; - - size_t y_plane_size = lumaStride * cinfo->image_height; - size_t u_plane_size = chromaStride * cinfo->image_height / 2; - uint8_t* y_plane = const_cast(yBuffer); - uint8_t* u_plane = const_cast(uvBuffer); - uint8_t* v_plane = const_cast(u_plane + u_plane_size); - std::unique_ptr empty = std::make_unique(cinfo->image_width); - memset(empty.get(), 0, cinfo->image_width); - - const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - const bool need_padding = (lumaStride < aligned_width); - std::unique_ptr buffer_intrm = nullptr; - uint8_t* y_plane_intrm = nullptr; - uint8_t* u_plane_intrm = nullptr; - uint8_t* v_plane_intrm = nullptr; - JSAMPROW y_intrm[kCompressBatchSize]; - JSAMPROW cb_intrm[kCompressBatchSize / 2]; - JSAMPROW cr_intrm[kCompressBatchSize / 2]; - JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm}; - if (need_padding) { - size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; - buffer_intrm = std::make_unique(mcu_row_size); - y_plane_intrm = buffer_intrm.get(); - u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); - v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; - for (int i = 0; i < kCompressBatchSize; ++i) { - y_intrm[i] = y_plane_intrm + i * aligned_width; - memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width); - } - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - int offset_intrm = i * (aligned_width / 2); - cb_intrm[i] = u_plane_intrm + offset_intrm; - cr_intrm[i] = v_plane_intrm + offset_intrm; - memset(cb_intrm[i] + cinfo->image_width / 2, 0, - (aligned_width - cinfo->image_width) / 2); - memset(cr_intrm[i] + cinfo->image_width / 2, 0, - (aligned_width - cinfo->image_width) / 2); - } - } - - while (cinfo->next_scanline < cinfo->image_height) { - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->next_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * lumaStride; - } else { - y[i] = empty.get(); - } - if (need_padding) { - memcpy(y_intrm[i], y[i], cinfo->image_width); - } - } - // cb, cr only have half scanlines - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - size_t scanline = cinfo->next_scanline / 2 + i; - if (scanline < cinfo->image_height / 2) { - int offset = scanline * chromaStride; - cb[i] = u_plane + offset; - cr[i] = v_plane + offset; - } else { - cb[i] = cr[i] = empty.get(); - } - if (need_padding) { - memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2); - memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2); - } - } - int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes, - kCompressBatchSize); - if (processed != kCompressBatchSize) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - } - return true; -} - -bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, - int lumaStride) { - JSAMPROW y[kCompressBatchSize]; - JSAMPARRAY planes[1]{y}; - - uint8_t* y_plane = const_cast(yBuffer); - std::unique_ptr empty = std::make_unique(cinfo->image_width); - memset(empty.get(), 0, cinfo->image_width); - - const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - const bool need_padding = (lumaStride < aligned_width); - std::unique_ptr buffer_intrm = nullptr; - uint8_t* y_plane_intrm = nullptr; - uint8_t* u_plane_intrm = nullptr; - JSAMPROW y_intrm[kCompressBatchSize]; - JSAMPARRAY planes_intrm[]{y_intrm}; - if (need_padding) { - size_t mcu_row_size = aligned_width * kCompressBatchSize; - buffer_intrm = std::make_unique(mcu_row_size); - y_plane_intrm = buffer_intrm.get(); - for (int i = 0; i < kCompressBatchSize; ++i) { - y_intrm[i] = y_plane_intrm + i * aligned_width; - memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width); - } - } - - while (cinfo->next_scanline < cinfo->image_height) { - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->next_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * lumaStride; - } else { - y[i] = empty.get(); - } - if (need_padding) { - memcpy(y_intrm[i], y[i], cinfo->image_width); - } - } - int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes, - kCompressBatchSize); - if (processed != kCompressBatchSize / 2) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - } - return true; -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp deleted file mode 100644 index 3d70fcea71..0000000000 --- a/libs/ultrahdr/jpegr.cpp +++ /dev/null @@ -1,1503 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -using namespace std; -using namespace photos_editing_formats::image_io; - -namespace android::ultrahdr { - -#define USE_SRGB_INVOETF_LUT 1 -#define USE_HLG_OETF_LUT 1 -#define USE_PQ_OETF_LUT 1 -#define USE_HLG_INVOETF_LUT 1 -#define USE_PQ_INVOETF_LUT 1 -#define USE_APPLY_GAIN_LUT 1 - -#define JPEGR_CHECK(x) \ - { \ - status_t status = (x); \ - if ((status) != NO_ERROR) { \ - return status; \ - } \ - } - -// JPEG compress quality (0 ~ 100) for gain map -static const int kMapCompressQuality = 85; - -#define CONFIG_MULTITHREAD 1 -int GetCPUCoreCount() { - int cpuCoreCount = 1; -#if CONFIG_MULTITHREAD -#if defined(_SC_NPROCESSORS_ONLN) - cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); -#else - // _SC_NPROC_ONLN must be defined... - cpuCoreCount = sysconf(_SC_NPROC_ONLN); -#endif -#endif - return cpuCoreCount; -} - -/* - * Helper function copies the JPEG image from without EXIF. - * - * @param pDest destination of the data to be written. - * @param pSource source of data being written. - * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos(). - * (4 bytes offset to FF sign, the byte after FF E1 XX XX ). - * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize(). - */ -static void copyJpegWithoutExif(jr_compressed_ptr pDest, - jr_compressed_ptr pSource, - size_t exif_pos, - size_t exif_size) { - const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign - pDest->length = pSource->length - exif_size - exif_offset; - pDest->data = new uint8_t[pDest->length]; - pDest->maxLength = pDest->length; - pDest->colorGamut = pSource->colorGamut; - memcpy(pDest->data, pSource->data, exif_pos - exif_offset); - memcpy((uint8_t*)pDest->data + exif_pos - exif_offset, - (uint8_t*)pSource->data + exif_pos + exif_size, - pSource->length - exif_pos - exif_size); -} - -status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest_ptr) { - if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) { - ALOGE("Received nullptr for input p010 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) { - ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width, - p010_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) { - ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth, - kMinHeight, p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) { - ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth, - kMaxHeight, p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || - p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { - ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) { - ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d", - p010_image_ptr->luma_stride, p010_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (p010_image_ptr->chroma_data != nullptr && - p010_image_ptr->chroma_stride < p010_image_ptr->width) { - ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d", - p010_image_ptr->chroma_stride, p010_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (dest_ptr == nullptr || dest_ptr->data == nullptr) { - ALOGE("Received nullptr for destination"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) { - ALOGE("Invalid hdr transfer function %d", hdr_tf); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (yuv420_image_ptr == nullptr) { - return NO_ERROR; - } - if (yuv420_image_ptr->data == nullptr) { - ALOGE("Received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (yuv420_image_ptr->luma_stride != 0 && - yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) { - ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d", - yuv420_image_ptr->luma_stride, yuv420_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (yuv420_image_ptr->chroma_data != nullptr && - yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) { - ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d", - yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (p010_image_ptr->width != yuv420_image_ptr->width || - p010_image_ptr->height != yuv420_image_ptr->height) { - ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width, - p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height); - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || - yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { - ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - return NO_ERROR; -} - -status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest_ptr, int quality) { - if (quality < 0 || quality > 100) { - ALOGE("quality factor is out side range [0-100], quality factor : %d", quality); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr); -} - -/* Encode API-0 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { - // validate input arguments - if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality); - ret != NO_ERROR) { - return ret; - } - if (exif != nullptr && exif->data == nullptr) { - ALOGE("received nullptr for exif metadata"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - - const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock); - unique_ptr yuv420_image_data = - make_unique(yu420_luma_stride * p010_image.height * 3 / 2); - jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(), - .width = p010_image.width, - .height = p010_image.height, - .colorGamut = p010_image.colorGamut, - .luma_stride = yu420_luma_stride, - .chroma_data = nullptr, - .chroma_stride = yu420_luma_stride >> 1}; - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; - - // tone map - JPEGR_CHECK(toneMap(&p010_image, &yuv420_image)); - - // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); - - // compress gain map - JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - - sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); - - // convert to Bt601 YUV encoding for JPEG encode - if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) { - JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3)); - } - - // compress 420 image - JpegEncoderHelper jpeg_enc_obj_yuv420; - if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast(yuv420_image.data), - reinterpret_cast(yuv420_image.chroma_data), - yuv420_image.width, yuv420_image.height, - yuv420_image.luma_stride, yuv420_image.chroma_stride, - quality, icc->getData(), icc->getLength())) { - return ERROR_JPEGR_ENCODE_ERROR; - } - jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(), - .length = static_cast( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .maxLength = static_cast( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .colorGamut = yuv420_image.colorGamut}; - - // append gain map, no ICC since JPEG encode already did it - JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, - &metadata, dest)); - - return NO_ERROR; -} - -/* Encode API-1 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { - // validate input arguments - if (yuv420_image_ptr == nullptr) { - ALOGE("received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (exif != nullptr && exif->data == nullptr) { - ALOGE("received nullptr for exif metadata"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality); - ret != NO_ERROR) { - return ret; - } - - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; - if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; - if (!yuv420_image.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; - yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; - } - - // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); - - // compress gain map - JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - - sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); - - jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image; - unique_ptr yuv_420_bt601_data; - // Convert to bt601 YUV encoding for JPEG encode - if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) { - const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock); - yuv_420_bt601_data = - make_unique(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2); - yuv420_bt601_image.data = yuv_420_bt601_data.get(); - yuv420_bt601_image.colorGamut = yuv420_image.colorGamut; - yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride; - uint8_t* data = reinterpret_cast(yuv420_bt601_image.data); - yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height; - yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1; - - { - // copy luma - uint8_t* y_dst = reinterpret_cast(yuv420_bt601_image.data); - uint8_t* y_src = reinterpret_cast(yuv420_image.data); - if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) { - memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height); - } else { - for (size_t i = 0; i < yuv420_image.height; i++) { - memcpy(y_dst, y_src, yuv420_image.width); - if (yuv420_image.width != yuv420_bt601_image.luma_stride) { - memset(y_dst + yuv420_image.width, 0, - yuv420_bt601_image.luma_stride - yuv420_image.width); - } - y_dst += yuv420_bt601_image.luma_stride; - y_src += yuv420_image.luma_stride; - } - } - } - - if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) { - // copy luma - uint8_t* ch_dst = reinterpret_cast(yuv420_bt601_image.chroma_data); - uint8_t* ch_src = reinterpret_cast(yuv420_image.chroma_data); - memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height); - } else { - // copy cb & cr - uint8_t* cb_dst = reinterpret_cast(yuv420_bt601_image.chroma_data); - uint8_t* cb_src = reinterpret_cast(yuv420_image.chroma_data); - uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2); - uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2); - for (size_t i = 0; i < yuv420_image.height / 2; i++) { - memcpy(cb_dst, cb_src, yuv420_image.width / 2); - memcpy(cr_dst, cr_src, yuv420_image.width / 2); - if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) { - memset(cb_dst + yuv420_image.width / 2, 0, - yuv420_bt601_image.chroma_stride - yuv420_image.width / 2); - memset(cr_dst + yuv420_image.width / 2, 0, - yuv420_bt601_image.chroma_stride - yuv420_image.width / 2); - } - cb_dst += yuv420_bt601_image.chroma_stride; - cb_src += yuv420_image.chroma_stride; - cr_dst += yuv420_bt601_image.chroma_stride; - cr_src += yuv420_image.chroma_stride; - } - } - JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3)); - } - - // compress 420 image - JpegEncoderHelper jpeg_enc_obj_yuv420; - if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast(yuv420_bt601_image.data), - reinterpret_cast(yuv420_bt601_image.chroma_data), - yuv420_bt601_image.width, yuv420_bt601_image.height, - yuv420_bt601_image.luma_stride, - yuv420_bt601_image.chroma_stride, quality, icc->getData(), - icc->getLength())) { - return ERROR_JPEGR_ENCODE_ERROR; - } - - jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(), - .length = static_cast( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .maxLength = static_cast( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .colorGamut = yuv420_image.colorGamut}; - - // append gain map, no ICC since JPEG encode already did it - JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, - &metadata, dest)); - return NO_ERROR; -} - -/* Encode API-2 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - jr_compressed_ptr yuv420jpg_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { - // validate input arguments - if (yuv420_image_ptr == nullptr) { - ALOGE("received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest); - ret != NO_ERROR) { - return ret; - } - - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; - if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; - if (!yuv420_image.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height; - yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; - } - - // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); - - // compress gain map - JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - - return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest); -} - -/* Encode API-3 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, - jr_compressed_ptr yuv420jpg_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { - // validate input arguments - if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) { - return ret; - } - - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - - // decode input jpeg, gamut is going to be bt601. - JpegDecoderHelper jpeg_dec_obj_yuv420; - if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data, - yuv420jpg_image_ptr->length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - jpegr_uncompressed_struct yuv420_image{}; - yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr(); - yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth(); - yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); - yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut; - if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; - if (!yuv420_image.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height; - yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; - } - - if (p010_image_ptr->width != yuv420_image.width || - p010_image_ptr->height != yuv420_image.height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - - // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image, - true /* sdr_is_601 */)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); - - // compress gain map - JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - - return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest); -} - -/* Encode API-4 */ -status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, - jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, - jr_compressed_ptr dest) { - if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed gain map"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (dest == nullptr || dest->data == nullptr) { - ALOGE("received nullptr for destination"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - // We just want to check if ICC is present, so don't do a full decode. Note, - // this doesn't verify that the ICC is valid. - JpegDecoderHelper decoder; - std::vector icc; - decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length, - /* pWidth */ nullptr, /* pHeight */ nullptr, &icc, - /* exifData */ nullptr); - - // Add ICC if not already present. - if (icc.size() > 0) { - JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr, - /* icc */ nullptr, /* icc size */ 0, metadata, dest)); - } else { - sp newIcc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut); - JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr, - newIcc->getData(), newIcc->getLength(), metadata, dest)); - } - - return NO_ERROR; -} - -status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) { - if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpegr image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (jpeg_image_info_ptr == nullptr) { - ALOGE("received nullptr for compressed jpegr info struct"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - jpegr_compressed_struct primary_image, gainmap_image; - status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image); - if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) { - return status; - } - - JpegDecoderHelper jpeg_dec_obj_hdr; - if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length, - &jpeg_image_info_ptr->width, - &jpeg_image_info_ptr->height, - jpeg_image_info_ptr->iccData, - jpeg_image_info_ptr->exifData)) { - return ERROR_JPEGR_DECODE_ERROR; - } - - return status; -} - -/* Decode API */ -status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, - float max_display_boost, jr_exif_ptr exif, - ultrahdr_output_format output_format, - jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) { - if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpegr image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (dest == nullptr || dest->data == nullptr) { - ALOGE("received nullptr for dest image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (max_display_boost < 1.0f) { - ALOGE("received bad value for max_display_boost %f", max_display_boost); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (exif != nullptr && exif->data == nullptr) { - ALOGE("received nullptr address for exif data"); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) { - ALOGE("received bad value for output format %d", output_format); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - - jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image; - status_t status = - extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image); - if (status != NO_ERROR) { - if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) { - ALOGE("received invalid compressed jpegr image"); - return status; - } - } - - JpegDecoderHelper jpeg_dec_obj_yuv420; - if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length, - (output_format == ULTRAHDR_OUTPUT_SDR))) { - return ERROR_JPEGR_DECODE_ERROR; - } - - if (output_format == ULTRAHDR_OUTPUT_SDR) { - if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * - jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) > - jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_CALCULATION_ERROR; - } - } else { - if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * - jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) > - jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_CALCULATION_ERROR; - } - } - - if (exif != nullptr) { - if (exif->data == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; - } - memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize()); - exif->length = jpeg_dec_obj_yuv420.getEXIFSize(); - } - - if (output_format == ULTRAHDR_OUTPUT_SDR) { - dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth(); - dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); - memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(), - dest->width * dest->height * 4); - return NO_ERROR; - } - - JpegDecoderHelper jpeg_dec_obj_gm; - if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) > - jpeg_dec_obj_gm.getDecompressedImageSize()) { - return ERROR_JPEGR_CALCULATION_ERROR; - } - - jpegr_uncompressed_struct gainmap_image; - gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr(); - gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth(); - gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight(); - - if (gainmap_image_ptr != nullptr) { - gainmap_image_ptr->width = gainmap_image.width; - gainmap_image_ptr->height = gainmap_image.height; - int size = gainmap_image_ptr->width * gainmap_image_ptr->height; - gainmap_image_ptr->data = malloc(size); - memcpy(gainmap_image_ptr->data, gainmap_image.data, size); - } - - ultrahdr_metadata_struct uhdr_metadata; - if (!getMetadataFromXMP(static_cast(jpeg_dec_obj_gm.getXMPPtr()), - jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) { - return ERROR_JPEGR_INVALID_METADATA; - } - - if (metadata != nullptr) { - metadata->version = uhdr_metadata.version; - metadata->minContentBoost = uhdr_metadata.minContentBoost; - metadata->maxContentBoost = uhdr_metadata.maxContentBoost; - metadata->gamma = uhdr_metadata.gamma; - metadata->offsetSdr = uhdr_metadata.offsetSdr; - metadata->offsetHdr = uhdr_metadata.offsetHdr; - metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin; - metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax; - } - - jpegr_uncompressed_struct yuv420_image; - yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr(); - yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth(); - yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); - yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(), - jpeg_dec_obj_yuv420.getICCSize()); - yuv420_image.luma_stride = yuv420_image.width; - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; - yuv420_image.chroma_stride = yuv420_image.width >> 1; - - JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format, - max_display_boost, dest)); - return NO_ERROR; -} - -status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, - JpegEncoderHelper* jpeg_enc_obj_ptr) { - if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - // Don't need to convert YUV to Bt601 since single channel - if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast(gainmap_image_ptr->data), nullptr, - gainmap_image_ptr->width, gainmap_image_ptr->height, - gainmap_image_ptr->luma_stride, 0, kMapCompressQuality, - nullptr, 0)) { - return ERROR_JPEGR_ENCODE_ERROR; - } - - return NO_ERROR; -} - -const int kJobSzInRows = 16; -static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0, - "align job size to kMapDimensionScaleFactor"); - -class JobQueue { -public: - bool dequeueJob(size_t& rowStart, size_t& rowEnd); - void enqueueJob(size_t rowStart, size_t rowEnd); - void markQueueForEnd(); - void reset(); - -private: - bool mQueuedAllJobs = false; - std::deque> mJobs; - std::mutex mMutex; - std::condition_variable mCv; -}; - -bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) { - std::unique_lock lock{mMutex}; - while (true) { - if (mJobs.empty()) { - if (mQueuedAllJobs) { - return false; - } else { - mCv.wait_for(lock, std::chrono::milliseconds(100)); - } - } else { - auto it = mJobs.begin(); - rowStart = std::get<0>(*it); - rowEnd = std::get<1>(*it); - mJobs.erase(it); - return true; - } - } - return false; -} - -void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) { - std::unique_lock lock{mMutex}; - mJobs.push_back(std::make_tuple(rowStart, rowEnd)); - lock.unlock(); - mCv.notify_one(); -} - -void JobQueue::markQueueForEnd() { - std::unique_lock lock{mMutex}; - mQueuedAllJobs = true; - lock.unlock(); - mCv.notify_all(); -} - -void JobQueue::reset() { - std::unique_lock lock{mMutex}; - mJobs.clear(); - mQueuedAllJobs = false; -} - -status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr p010_image_ptr, - ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr dest, bool sdr_is_601) { - if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr || - dest == nullptr || yuv420_image_ptr->data == nullptr || - yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr || - p010_image_ptr->chroma_data == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (yuv420_image_ptr->width != p010_image_ptr->width || - yuv420_image_ptr->height != p010_image_ptr->height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED || - p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - size_t image_width = yuv420_image_ptr->width; - size_t image_height = yuv420_image_ptr->height; - size_t map_width = image_width / kMapDimensionScaleFactor; - size_t map_height = image_height / kMapDimensionScaleFactor; - - dest->data = new uint8_t[map_width * map_height]; - dest->width = map_width; - dest->height = map_height; - dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - dest->luma_stride = map_width; - dest->chroma_data = nullptr; - dest->chroma_stride = 0; - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(dest->data)); - - ColorTransformFn hdrInvOetf = nullptr; - float hdr_white_nits; - switch (hdr_tf) { - case ULTRAHDR_TF_LINEAR: - hdrInvOetf = identityConversion; - // Note: this will produce clipping if the input exceeds kHlgMaxNits. - // TODO: TF LINEAR will be deprecated. - hdr_white_nits = kHlgMaxNits; - break; - case ULTRAHDR_TF_HLG: -#if USE_HLG_INVOETF_LUT - hdrInvOetf = hlgInvOetfLUT; -#else - hdrInvOetf = hlgInvOetf; -#endif - hdr_white_nits = kHlgMaxNits; - break; - case ULTRAHDR_TF_PQ: -#if USE_PQ_INVOETF_LUT - hdrInvOetf = pqInvOetfLUT; -#else - hdrInvOetf = pqInvOetf; -#endif - hdr_white_nits = kPqMaxNits; - break; - default: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_TRANS_FUNC; - } - - metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits; - metadata->minContentBoost = 1.0f; - metadata->gamma = 1.0f; - metadata->offsetSdr = 0.0f; - metadata->offsetHdr = 0.0f; - metadata->hdrCapacityMin = 1.0f; - metadata->hdrCapacityMax = metadata->maxContentBoost; - - float log2MinBoost = log2(metadata->minContentBoost); - float log2MaxBoost = log2(metadata->maxContentBoost); - - ColorTransformFn hdrGamutConversionFn = - getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut); - - ColorCalculationFn luminanceFn = nullptr; - ColorTransformFn sdrYuvToRgbFn = nullptr; - switch (yuv420_image_ptr->colorGamut) { - case ULTRAHDR_COLORGAMUT_BT709: - luminanceFn = srgbLuminance; - sdrYuvToRgbFn = srgbYuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_P3: - luminanceFn = p3Luminance; - sdrYuvToRgbFn = p3YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - luminanceFn = bt2100Luminance; - sdrYuvToRgbFn = bt2100YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - if (sdr_is_601) { - sdrYuvToRgbFn = p3YuvToRgb; - } - - ColorTransformFn hdrYuvToRgbFn = nullptr; - switch (p010_image_ptr->colorGamut) { - case ULTRAHDR_COLORGAMUT_BT709: - hdrYuvToRgbFn = srgbYuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_P3: - hdrYuvToRgbFn = p3YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - hdrYuvToRgbFn = bt2100YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - std::mutex mutex; - const int threads = std::clamp(GetCPUCoreCount(), 1, 4); - size_t rowStep = threads == 1 ? image_height : kJobSzInRows; - JobQueue jobQueue; - - std::function generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf, - hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, - hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost, - &jobQueue]() -> void { - size_t rowStart, rowEnd; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < dest->width; ++x) { - Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y); - Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); - // We are assuming the SDR input is always sRGB transfer. -#if USE_SRGB_INVOETF_LUT - Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); -#else - Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); -#endif - float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; - - Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y); - Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); - Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); - hdr_rgb = hdrGamutConversionFn(hdr_rgb); - float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; - - size_t pixel_idx = x + y * dest->width; - reinterpret_cast(dest->data)[pixel_idx] = - encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); - } - } - } - }; - - // generate map - std::vector workers; - for (int th = 0; th < threads - 1; th++) { - workers.push_back(std::thread(generateMap)); - } - - rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor; - for (size_t rowStart = 0; rowStart < map_height;) { - size_t rowEnd = std::min(rowStart + rowStep, map_height); - jobQueue.enqueueJob(rowStart, rowEnd); - rowStart = rowEnd; - } - jobQueue.markQueueForEnd(); - generateMap(); - std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - - map_data.release(); - return NO_ERROR; -} - -status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata, - ultrahdr_output_format output_format, float max_display_boost, - jr_uncompressed_ptr dest) { - if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr || - dest == nullptr || yuv420_image_ptr->data == nullptr || - yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (metadata->version.compare(kJpegrVersion)) { - ALOGE("Unsupported metadata version: %s", metadata->version.c_str()); - return ERROR_JPEGR_UNSUPPORTED_METADATA; - } - if (metadata->gamma != 1.0f) { - ALOGE("Unsupported metadata gamma: %f", metadata->gamma); - return ERROR_JPEGR_UNSUPPORTED_METADATA; - } - if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) { - ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr); - return ERROR_JPEGR_UNSUPPORTED_METADATA; - } - if (metadata->hdrCapacityMin != metadata->minContentBoost || - metadata->hdrCapacityMax != metadata->maxContentBoost) { - ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin, - metadata->hdrCapacityMax); - return ERROR_JPEGR_UNSUPPORTED_METADATA; - } - - // TODO: remove once map scaling factor is computed based on actual map dims - size_t image_width = yuv420_image_ptr->width; - size_t image_height = yuv420_image_ptr->height; - size_t map_width = image_width / kMapDimensionScaleFactor; - size_t map_height = image_height / kMapDimensionScaleFactor; - if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) { - ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map " - "resolution is %dx%d, received gain map resolution is %dx%d", - (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - - dest->width = yuv420_image_ptr->width; - dest->height = yuv420_image_ptr->height; - ShepardsIDW idwTable(kMapDimensionScaleFactor); - float display_boost = std::min(max_display_boost, metadata->maxContentBoost); - GainLUT gainLUT(metadata, display_boost); - - JobQueue jobQueue; - std::function applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest, - &jobQueue, &idwTable, output_format, &gainLUT, - display_boost]() -> void { - size_t width = yuv420_image_ptr->width; - size_t height = yuv420_image_ptr->height; - - size_t rowStart, rowEnd; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < width; ++x) { - Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y); - // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients - Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); - // We are assuming the SDR base image is always sRGB transfer. -#if USE_SRGB_INVOETF_LUT - Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr); -#else - Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr); -#endif - float gain; - // TODO: determine map scaling factor based on actual map dims - size_t map_scale_factor = kMapDimensionScaleFactor; - // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following. - // Currently map_scale_factor is of type size_t, but it could be changed to a float - // later. - if (map_scale_factor != floorf(map_scale_factor)) { - gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y); - } else { - gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable); - } - -#if USE_APPLY_GAIN_LUT - Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT); -#else - Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost); -#endif - rgb_hdr = rgb_hdr / display_boost; - size_t pixel_idx = x + y * width; - - switch (output_format) { - case ULTRAHDR_OUTPUT_HDR_LINEAR: { - uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr); - reinterpret_cast(dest->data)[pixel_idx] = rgba_f16; - break; - } - case ULTRAHDR_OUTPUT_HDR_HLG: { -#if USE_HLG_OETF_LUT - ColorTransformFn hdrOetf = hlgOetfLUT; -#else - ColorTransformFn hdrOetf = hlgOetf; -#endif - Color rgb_gamma_hdr = hdrOetf(rgb_hdr); - uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); - reinterpret_cast(dest->data)[pixel_idx] = rgba_1010102; - break; - } - case ULTRAHDR_OUTPUT_HDR_PQ: { -#if USE_PQ_OETF_LUT - ColorTransformFn hdrOetf = pqOetfLUT; -#else - ColorTransformFn hdrOetf = pqOetf; -#endif - Color rgb_gamma_hdr = hdrOetf(rgb_hdr); - uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); - reinterpret_cast(dest->data)[pixel_idx] = rgba_1010102; - break; - } - default: { - } - // Should be impossible to hit after input validation. - } - } - } - } - }; - - const int threads = std::clamp(GetCPUCoreCount(), 1, 4); - std::vector workers; - for (int th = 0; th < threads - 1; th++) { - workers.push_back(std::thread(applyRecMap)); - } - const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows; - for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) { - int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height); - jobQueue.enqueueJob(rowStart, rowEnd); - rowStart = rowEnd; - } - jobQueue.markQueueForEnd(); - applyRecMap(); - std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - return NO_ERROR; -} - -status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, - jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr) { - if (jpegr_image_ptr == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - - MessageHandler msg_handler; - std::shared_ptr seg = - DataSegment::Create(DataRange(0, jpegr_image_ptr->length), - static_cast(jpegr_image_ptr->data), - DataSegment::BufferDispositionPolicy::kDontDelete); - DataSegmentDataSource data_source(seg); - JpegInfoBuilder jpeg_info_builder; - jpeg_info_builder.SetImageLimit(2); - JpegScanner jpeg_scanner(&msg_handler); - jpeg_scanner.Run(&data_source, &jpeg_info_builder); - data_source.Reset(); - - if (jpeg_scanner.HasError()) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - - const auto& jpeg_info = jpeg_info_builder.GetInfo(); - const auto& image_ranges = jpeg_info.GetImageRanges(); - - if (image_ranges.empty()) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - - if (primary_jpg_image_ptr != nullptr) { - primary_jpg_image_ptr->data = - static_cast(jpegr_image_ptr->data) + image_ranges[0].GetBegin(); - primary_jpg_image_ptr->length = image_ranges[0].GetLength(); - } - - if (image_ranges.size() == 1) { - return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND; - } - - if (gainmap_jpg_image_ptr != nullptr) { - gainmap_jpg_image_ptr->data = - static_cast(jpegr_image_ptr->data) + image_ranges[1].GetBegin(); - gainmap_jpg_image_ptr->length = image_ranges[1].GetLength(); - } - - // TODO: choose primary image and gain map image carefully - if (image_ranges.size() > 2) { - ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen", - (int)image_ranges.size()); - } - - return NO_ERROR; -} - -// JPEG/R structure: -// SOI (ff d8) -// -// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents -// in the JPEG input (Encode API-2, API-3, API-4)) -// APP1 (ff e1) -// 2 bytes of length (2 + length of exif package) -// EXIF package (this includes the first two bytes representing the package length) -// -// (Required, XMP package) APP1 (ff e1) -// 2 bytes of length (2 + 29 + length of xmp package) -// name space ("http://ns.adobe.com/xap/1.0/\0") -// XMP -// -// (Required, MPF package) APP2 (ff e2) -// 2 bytes of length -// MPF -// -// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages) -// -// SOI (ff d8) -// -// (Required, XMP package) APP1 (ff e1) -// 2 bytes of length (2 + 29 + length of xmp package) -// name space ("http://ns.adobe.com/xap/1.0/\0") -// XMP -// -// (Required) secondary image (the gain map, without the first two bytes (SOI)) -// -// Metadata versions we are using: -// ECMA TR-98 for JFIF marker -// Exif 2.2 spec for EXIF marker -// Adobe XMP spec part 3 for XMP marker -// ICC v4.3 spec for ICC -status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, - void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata, - jr_compressed_ptr dest) { - if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr || - dest == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (metadata->version.compare("1.0")) { - ALOGE("received bad value for version: %s", metadata->version.c_str()); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (metadata->maxContentBoost < metadata->minContentBoost) { - ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost, - metadata->maxContentBoost); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) { - ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin, - metadata->hdrCapacityMax); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) { - ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - if (metadata->gamma <= 0.0f) { - ALOGE("received bad value for gamma %f", metadata->gamma); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - - const string nameSpace = "http://ns.adobe.com/xap/1.0/"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - - // calculate secondary image length first, because the length will be written into the primary - // image xmp - const string xmp_secondary = generateXmpForSecondaryImage(*metadata); - const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */ - + nameSpaceLength /* 29 bytes length of name space including \0 */ - + xmp_secondary.size(); /* length of xmp packet */ - const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ - + xmp_secondary_length + gainmap_jpg_image_ptr->length; - // primary image - const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata); - // same as primary - const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size(); - - // Check if EXIF package presents in the JPEG input. - // If so, extract and remove the EXIF package. - JpegDecoderHelper decoder; - if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0}; - jpegr_compressed_struct new_jpg_image = {.data = nullptr, - .length = 0, - .maxLength = 0, - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - std::unique_ptr dest_data; - if (decoder.getEXIFPos() >= 0) { - if (pExif != nullptr) { - ALOGE("received EXIF from outside while the primary image already contains EXIF"); - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - copyJpegWithoutExif(&new_jpg_image, - primary_jpg_image_ptr, - decoder.getEXIFPos(), - decoder.getEXIFSize()); - dest_data.reset(reinterpret_cast(new_jpg_image.data)); - exif_from_jpg.data = decoder.getEXIFPtr(); - exif_from_jpg.length = decoder.getEXIFSize(); - pExif = &exif_from_jpg; - } - - jr_compressed_ptr final_primary_jpg_image_ptr = - new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image; - - int pos = 0; - // Begin primary image - // Write SOI - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); - - // Write EXIF - if (pExif != nullptr) { - const int length = 2 + pExif->length; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos)); - } - - // Prepare and write XMP - { - const int length = xmp_primary_length; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); - } - - // Write ICC - if (pIcc != nullptr && icc_size > 0) { - const int length = icc_size + 2; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, pIcc, icc_size, pos)); - } - - // Prepare and write MPF - { - const int length = 2 + calculateMpfSize(); - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - int primary_image_size = pos + length + final_primary_jpg_image_ptr->length; - // between APP2 + package size + signature - // ff e2 00 58 4d 50 46 00 - // 2 + 2 + 4 = 8 (bytes) - // and ff d8 sign of the secondary image - int secondary_image_offset = primary_image_size - pos - 8; - sp mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */ - secondary_image_size, secondary_image_offset); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos)); - } - - // Write primary image - JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2, - final_primary_jpg_image_ptr->length - 2, pos)); - // Finish primary image - - // Begin secondary image (gain map) - // Write SOI - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); - - // Prepare and write XMP - { - const int length = xmp_secondary_length; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos)); - } - - // Write secondary image - JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2, - gainmap_jpg_image_ptr->length - 2, pos)); - - // Set back length - dest->length = pos; - - // Done! - return NO_ERROR; -} - -status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { - if (src == nullptr || dest == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (src->width != dest->width || src->height != dest->height) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; - } - uint16_t* src_y_data = reinterpret_cast(src->data); - uint8_t* dst_y_data = reinterpret_cast(dest->data); - for (size_t y = 0; y < src->height; ++y) { - uint16_t* src_y_row = src_y_data + y * src->luma_stride; - uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride; - for (size_t x = 0; x < src->width; ++x) { - uint16_t y_uint = src_y_row[x] >> 6; - dst_y_row[x] = static_cast((y_uint >> 2) & 0xff); - } - if (dest->width != dest->luma_stride) { - memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width); - } - } - uint16_t* src_uv_data = reinterpret_cast(src->chroma_data); - uint8_t* dst_u_data = reinterpret_cast(dest->chroma_data); - size_t dst_v_offset = (dest->chroma_stride * dest->height / 2); - uint8_t* dst_v_data = dst_u_data + dst_v_offset; - for (size_t y = 0; y < src->height / 2; ++y) { - uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride; - uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride; - uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride; - for (size_t x = 0; x < src->width / 2; ++x) { - uint16_t u_uint = src_uv_row[x << 1] >> 6; - uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6; - dst_u_row[x] = static_cast((u_uint >> 2) & 0xff); - dst_v_row[x] = static_cast((v_uint >> 2) & 0xff); - } - if (dest->width / 2 != dest->chroma_stride) { - memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2); - memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2); - } - } - dest->colorGamut = src->colorGamut; - return NO_ERROR; -} - -status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dest_encoding) { - if (image == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED || - dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - ColorTransformFn conversionFn = nullptr; - switch (src_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - switch (dest_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - return NO_ERROR; - case ULTRAHDR_COLORGAMUT_P3: - conversionFn = yuv709To601; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - conversionFn = yuv709To2100; - break; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - break; - case ULTRAHDR_COLORGAMUT_P3: - switch (dest_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - conversionFn = yuv601To709; - break; - case ULTRAHDR_COLORGAMUT_P3: - return NO_ERROR; - case ULTRAHDR_COLORGAMUT_BT2100: - conversionFn = yuv601To2100; - break; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - break; - case ULTRAHDR_COLORGAMUT_BT2100: - switch (dest_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - conversionFn = yuv2100To709; - break; - case ULTRAHDR_COLORGAMUT_P3: - conversionFn = yuv2100To601; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - return NO_ERROR; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - break; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - if (conversionFn == nullptr) { - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - for (size_t y = 0; y < image->height / 2; ++y) { - for (size_t x = 0; x < image->width / 2; ++x) { - transformYuv420(image, x, y, conversionFn); - } - } - - return NO_ERROR; -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp deleted file mode 100644 index c434eb6459..0000000000 --- a/libs/ultrahdr/jpegrutils.cpp +++ /dev/null @@ -1,600 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace photos_editing_formats::image_io; -using namespace std; - -namespace android::ultrahdr { -/* - * Helper function used for generating XMP metadata. - * - * @param prefix The prefix part of the name. - * @param suffix The suffix part of the name. - * @return A name of the form "prefix:suffix". - */ -static inline string Name(const string &prefix, const string &suffix) { - std::stringstream ss; - ss << prefix << ":" << suffix; - return ss.str(); -} - -DataStruct::DataStruct(int s) { - data = malloc(s); - length = s; - memset(data, 0, s); - writePos = 0; -} - -DataStruct::~DataStruct() { - if (data != nullptr) { - free(data); - } -} - -void* DataStruct::getData() { - return data; -} - -int DataStruct::getLength() { - return length; -} - -int DataStruct::getBytesWritten() { - return writePos; -} - -bool DataStruct::write8(uint8_t value) { - uint8_t v = value; - return write(&v, 1); -} - -bool DataStruct::write16(uint16_t value) { - uint16_t v = value; - return write(&v, 2); -} -bool DataStruct::write32(uint32_t value) { - uint32_t v = value; - return write(&v, 4); -} - -bool DataStruct::write(const void* src, int size) { - if (writePos + size > length) { - ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", - writePos, size, length); - return false; - } - memcpy((uint8_t*) data + writePos, src, size); - writePos += size; - return true; -} - -/* - * Helper function used for writing data to destination. - */ -status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { - if (position + length > destination->maxLength) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; - } - - memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); - position += length; - return NO_ERROR; -} - -// Extremely simple XML Handler - just searches for interesting elements -class XMPXmlHandler : public XmlHandler { -public: - - XMPXmlHandler() : XmlHandler() { - state = NotStrarted; - versionFound = false; - minContentBoostFound = false; - maxContentBoostFound = false; - gammaFound = false; - offsetSdrFound = false; - offsetHdrFound = false; - hdrCapacityMinFound = false; - hdrCapacityMaxFound = false; - baseRenditionIsHdrFound = false; - } - - enum ParseState { - NotStrarted, - Started, - Done - }; - - virtual DataMatchResult StartElement(const XmlTokenContext& context) { - string val; - if (context.BuildTokenValue(&val)) { - if (!val.compare(containerName)) { - state = Started; - } else { - if (state != Done) { - state = NotStrarted; - } - } - } - return context.GetResult(); - } - - virtual DataMatchResult FinishElement(const XmlTokenContext& context) { - if (state == Started) { - state = Done; - lastAttributeName = ""; - } - return context.GetResult(); - } - - virtual DataMatchResult AttributeName(const XmlTokenContext& context) { - string val; - if (state == Started) { - if (context.BuildTokenValue(&val)) { - if (!val.compare(versionAttrName)) { - lastAttributeName = versionAttrName; - } else if (!val.compare(maxContentBoostAttrName)) { - lastAttributeName = maxContentBoostAttrName; - } else if (!val.compare(minContentBoostAttrName)) { - lastAttributeName = minContentBoostAttrName; - } else if (!val.compare(gammaAttrName)) { - lastAttributeName = gammaAttrName; - } else if (!val.compare(offsetSdrAttrName)) { - lastAttributeName = offsetSdrAttrName; - } else if (!val.compare(offsetHdrAttrName)) { - lastAttributeName = offsetHdrAttrName; - } else if (!val.compare(hdrCapacityMinAttrName)) { - lastAttributeName = hdrCapacityMinAttrName; - } else if (!val.compare(hdrCapacityMaxAttrName)) { - lastAttributeName = hdrCapacityMaxAttrName; - } else if (!val.compare(baseRenditionIsHdrAttrName)) { - lastAttributeName = baseRenditionIsHdrAttrName; - } else { - lastAttributeName = ""; - } - } - } - return context.GetResult(); - } - - virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { - string val; - if (state == Started) { - if (context.BuildTokenValue(&val, true)) { - if (!lastAttributeName.compare(versionAttrName)) { - versionStr = val; - versionFound = true; - } else if (!lastAttributeName.compare(maxContentBoostAttrName)) { - maxContentBoostStr = val; - maxContentBoostFound = true; - } else if (!lastAttributeName.compare(minContentBoostAttrName)) { - minContentBoostStr = val; - minContentBoostFound = true; - } else if (!lastAttributeName.compare(gammaAttrName)) { - gammaStr = val; - gammaFound = true; - } else if (!lastAttributeName.compare(offsetSdrAttrName)) { - offsetSdrStr = val; - offsetSdrFound = true; - } else if (!lastAttributeName.compare(offsetHdrAttrName)) { - offsetHdrStr = val; - offsetHdrFound = true; - } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) { - hdrCapacityMinStr = val; - hdrCapacityMinFound = true; - } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) { - hdrCapacityMaxStr = val; - hdrCapacityMaxFound = true; - } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) { - baseRenditionIsHdrStr = val; - baseRenditionIsHdrFound = true; - } - } - } - return context.GetResult(); - } - - bool getVersion(string* version, bool* present) { - if (state == Done) { - *version = versionStr; - *present = versionFound; - return true; - } else { - return false; - } - } - - bool getMaxContentBoost(float* max_content_boost, bool* present) { - if (state == Done) { - *present = maxContentBoostFound; - stringstream ss(maxContentBoostStr); - float val; - if (ss >> val) { - *max_content_boost = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - bool getMinContentBoost(float* min_content_boost, bool* present) { - if (state == Done) { - *present = minContentBoostFound; - stringstream ss(minContentBoostStr); - float val; - if (ss >> val) { - *min_content_boost = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - bool getGamma(float* gamma, bool* present) { - if (state == Done) { - *present = gammaFound; - stringstream ss(gammaStr); - float val; - if (ss >> val) { - *gamma = val; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getOffsetSdr(float* offset_sdr, bool* present) { - if (state == Done) { - *present = offsetSdrFound; - stringstream ss(offsetSdrStr); - float val; - if (ss >> val) { - *offset_sdr = val; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getOffsetHdr(float* offset_hdr, bool* present) { - if (state == Done) { - *present = offsetHdrFound; - stringstream ss(offsetHdrStr); - float val; - if (ss >> val) { - *offset_hdr = val; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) { - if (state == Done) { - *present = hdrCapacityMinFound; - stringstream ss(hdrCapacityMinStr); - float val; - if (ss >> val) { - *hdr_capacity_min = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) { - if (state == Done) { - *present = hdrCapacityMaxFound; - stringstream ss(hdrCapacityMaxStr); - float val; - if (ss >> val) { - *hdr_capacity_max = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) { - if (state == Done) { - *present = baseRenditionIsHdrFound; - if (!baseRenditionIsHdrStr.compare("False")) { - *base_rendition_is_hdr = false; - return true; - } else if (!baseRenditionIsHdrStr.compare("True")) { - *base_rendition_is_hdr = true; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - -private: - static const string containerName; - - static const string versionAttrName; - string versionStr; - bool versionFound; - static const string maxContentBoostAttrName; - string maxContentBoostStr; - bool maxContentBoostFound; - static const string minContentBoostAttrName; - string minContentBoostStr; - bool minContentBoostFound; - static const string gammaAttrName; - string gammaStr; - bool gammaFound; - static const string offsetSdrAttrName; - string offsetSdrStr; - bool offsetSdrFound; - static const string offsetHdrAttrName; - string offsetHdrStr; - bool offsetHdrFound; - static const string hdrCapacityMinAttrName; - string hdrCapacityMinStr; - bool hdrCapacityMinFound; - static const string hdrCapacityMaxAttrName; - string hdrCapacityMaxStr; - bool hdrCapacityMaxFound; - static const string baseRenditionIsHdrAttrName; - string baseRenditionIsHdrStr; - bool baseRenditionIsHdrFound; - - string lastAttributeName; - ParseState state; -}; - -// GContainer XMP constants - URI and namespace prefix -const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; -const string kContainerPrefix = "Container"; - -// GContainer XMP constants - element and attribute names -const string kConDirectory = Name(kContainerPrefix, "Directory"); -const string kConItem = Name(kContainerPrefix, "Item"); - -// GContainer XMP constants - names for XMP handlers -const string XMPXmlHandler::containerName = "rdf:Description"; -// Item XMP constants - URI and namespace prefix -const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; -const string kItemPrefix = "Item"; - -// Item XMP constants - element and attribute names -const string kItemLength = Name(kItemPrefix, "Length"); -const string kItemMime = Name(kItemPrefix, "Mime"); -const string kItemSemantic = Name(kItemPrefix, "Semantic"); - -// Item XMP constants - element and attribute values -const string kSemanticPrimary = "Primary"; -const string kSemanticGainMap = "GainMap"; -const string kMimeImageJpeg = "image/jpeg"; - -// GainMap XMP constants - URI and namespace prefix -const string kGainMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; -const string kGainMapPrefix = "hdrgm"; - -// GainMap XMP constants - element and attribute names -const string kMapVersion = Name(kGainMapPrefix, "Version"); -const string kMapGainMapMin = Name(kGainMapPrefix, "GainMapMin"); -const string kMapGainMapMax = Name(kGainMapPrefix, "GainMapMax"); -const string kMapGamma = Name(kGainMapPrefix, "Gamma"); -const string kMapOffsetSdr = Name(kGainMapPrefix, "OffsetSDR"); -const string kMapOffsetHdr = Name(kGainMapPrefix, "OffsetHDR"); -const string kMapHDRCapacityMin = Name(kGainMapPrefix, "HDRCapacityMin"); -const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax"); -const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR"); - -// GainMap XMP constants - names for XMP handlers -const string XMPXmlHandler::versionAttrName = kMapVersion; -const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; -const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; -const string XMPXmlHandler::gammaAttrName = kMapGamma; -const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr; -const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr; -const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin; -const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax; -const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR; - -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) { - string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - - if (xmp_size < nameSpace.size()+2) { - // Data too short - return false; - } - - if (strncmp(reinterpret_cast(xmp_data), nameSpace.c_str(), nameSpace.size())) { - // Not correct namespace - return false; - } - - // Position the pointers to the start of XMP XML portion - xmp_data += nameSpace.size()+1; - xmp_size -= nameSpace.size()+1; - XMPXmlHandler handler; - - // We need to remove tail data until the closing tag. Otherwise parser will throw an error. - while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) { - xmp_size--; - } - - string str(reinterpret_cast(xmp_data), xmp_size); - MessageHandler msg_handler; - unique_ptr rule(new XmlElementRule); - XmlReader reader(&handler, &msg_handler); - reader.StartParse(std::move(rule)); - reader.Parse(str); - reader.FinishParse(); - if (reader.HasErrors()) { - // Parse error - return false; - } - - // Apply default values to any not-present fields, except for Version, - // maxContentBoost, and hdrCapacityMax, which are required. Return false if - // we encounter a present field that couldn't be parsed, since this - // indicates it is invalid (eg. string where there should be a float). - bool present = false; - if (!handler.getVersion(&metadata->version, &present) || !present) { - return false; - } - if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) { - return false; - } - if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) { - return false; - } - if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) { - if (present) return false; - metadata->minContentBoost = 1.0f; - } - if (!handler.getGamma(&metadata->gamma, &present)) { - if (present) return false; - metadata->gamma = 1.0f; - } - if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) { - if (present) return false; - metadata->offsetSdr = 1.0f / 64.0f; - } - if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) { - if (present) return false; - metadata->offsetHdr = 1.0f / 64.0f; - } - if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) { - if (present) return false; - metadata->hdrCapacityMin = 1.0f; - } - - bool base_rendition_is_hdr; - if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) { - if (present) return false; - base_rendition_is_hdr = false; - } - if (base_rendition_is_hdr) { - ALOGE("Base rendition of HDR is not supported!"); - return false; - } - - return true; -} - -string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) { - const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); - const vector kLiItem({string("rdf:li"), kConItem}); - - std::stringstream ss; - photos_editing_formats::image_io::XmlWriter writer(ss); - writer.StartWritingElement("x:xmpmeta"); - writer.WriteXmlns("x", "adobe:ns:meta/"); - writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); - writer.StartWritingElement("rdf:RDF"); - writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); - writer.StartWritingElement("rdf:Description"); - writer.WriteXmlns(kContainerPrefix, kContainerUri); - writer.WriteXmlns(kItemPrefix, kItemUri); - writer.WriteXmlns(kGainMapPrefix, kGainMapUri); - writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - - writer.StartWritingElements(kConDirSeq); - - size_t item_depth = writer.StartWritingElement("rdf:li"); - writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); - writer.StartWritingElement(kConItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); - writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); - writer.FinishWritingElementsToDepth(item_depth); - - writer.StartWritingElement("rdf:li"); - writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); - writer.StartWritingElement(kConItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap); - writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); - writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); - - writer.FinishWriting(); - - return ss.str(); -} - -string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { - const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); - - std::stringstream ss; - photos_editing_formats::image_io::XmlWriter writer(ss); - writer.StartWritingElement("x:xmpmeta"); - writer.WriteXmlns("x", "adobe:ns:meta/"); - writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); - writer.StartWritingElement("rdf:RDF"); - writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); - writer.StartWritingElement("rdf:Description"); - writer.WriteXmlns(kGainMapPrefix, kGainMapUri); - writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); - writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); - writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma); - writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr); - writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin)); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax)); - writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); - writer.FinishWriting(); - - return ss.str(); -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp deleted file mode 100644 index f1679ef1b3..0000000000 --- a/libs/ultrahdr/multipictureformat.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include - -namespace android::ultrahdr { -size_t calculateMpfSize() { - return sizeof(kMpfSig) + // Signature - kMpEndianSize + // Endianness - sizeof(uint32_t) + // Index IFD Offset - sizeof(uint16_t) + // Tag count - kTagSerializedCount * kTagSize + // 3 tags at 12 bytes each - sizeof(uint32_t) + // Attribute IFD offset - kNumPictures * kMPEntrySize; // MP Entries for each image -} - -sp generateMpf(int primary_image_size, int primary_image_offset, - int secondary_image_size, int secondary_image_offset) { - size_t mpf_size = calculateMpfSize(); - sp dataStruct = sp::make(mpf_size); - - dataStruct->write(static_cast(kMpfSig), sizeof(kMpfSig)); -#if USE_BIG_ENDIAN - dataStruct->write(static_cast(kMpBigEndian), kMpEndianSize); -#else - dataStruct->write(static_cast(kMpLittleEndian), kMpEndianSize); -#endif - - // Set the Index IFD offset be the position after the endianness value and this offset. - constexpr uint32_t indexIfdOffset = - static_cast(kMpEndianSize + sizeof(kMpfSig)); - dataStruct->write32(Endian_SwapBE32(indexIfdOffset)); - - // We will write 3 tags (version, number of images, MP entries). - dataStruct->write16(Endian_SwapBE16(kTagSerializedCount)); - - // Write the version tag. - dataStruct->write16(Endian_SwapBE16(kVersionTag)); - dataStruct->write16(Endian_SwapBE16(kVersionType)); - dataStruct->write32(Endian_SwapBE32(kVersionCount)); - dataStruct->write(kVersionExpected, kVersionSize); - - // Write the number of images. - dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag)); - dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType)); - dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount)); - dataStruct->write32(Endian_SwapBE32(kNumPictures)); - - // Write the MP entries. - dataStruct->write16(Endian_SwapBE16(kMPEntryTag)); - dataStruct->write16(Endian_SwapBE16(kMPEntryType)); - dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures)); - const uint32_t mpEntryOffset = - static_cast(dataStruct->getBytesWritten() - // The bytes written so far - sizeof(kMpfSig) + // Excluding the MPF signature - sizeof(uint32_t) + // The 4 bytes for this offset - sizeof(uint32_t)); // The 4 bytes for the attribute IFD offset. - dataStruct->write32(Endian_SwapBE32(mpEntryOffset)); - - // Write the attribute IFD offset (zero because we don't write it). - dataStruct->write32(0); - - // Write the MP entries for primary image - dataStruct->write32( - Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary)); - dataStruct->write32(Endian_SwapBE32(primary_image_size)); - dataStruct->write32(Endian_SwapBE32(primary_image_offset)); - dataStruct->write16(0); - dataStruct->write16(0); - - // Write the MP entries for secondary image - dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg)); - dataStruct->write32(Endian_SwapBE32(secondary_image_size)); - dataStruct->write32(Endian_SwapBE32(secondary_image_offset)); - dataStruct->write16(0); - dataStruct->write16(0); - - return dataStruct; -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml deleted file mode 100644 index 1754a5ccb9..0000000000 --- a/libs/ultrahdr/tests/AndroidTest.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - diff --git a/libs/ultrahdr/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg deleted file mode 100644 index e2857425e7734496457a483e4f11ea284c5a8773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24430 zcmex=o+2Ft9N&FfxOr7#LU? z7#N}K3rq|Q{EX}%S%xeI1_l8}b_P)f28IF#1_mQWCI%4(28J*O28PUb7O>tn1_&@R zFfy_-G_*1@QZO>MGBC9=Heztk&ri=ubuKL_$w^JiEY47H_YGF4urP}^Ghq;8nAOAz zHsK5-1H)V>`w1fhgL((VRVg5YC$KZHFfcF}7#SHbPk`7yg^_`Q1N1`HFC*prahj0+%o z|NUoRU|In2H7LZGp=?lyF)e|p0Xdl&iOsSE;+`Xn3=FJL@#E$U3_MUaBS-@%WU+)E zwh(>Mz`!7&4$%bCI}H>=>I@8P-ay2^Fhb~rTnNou0-^ukW^iWUU}IxvW949HXXoVP z;NlVC<>BV$krWc<7m<~cmy?x}kx@|5Q&&*ZQI?U>FxS*EFf=hSkyp30wJ@^LGd3{- z8N$fP$;rdbBf-lnVWcRdXhbsje}F-dgXsb*12dx%1Ct;lvmoRDBMkBk42-OdU;yzP z0|O%yGYcylI|nBh_x~ddTLl=H7@3)wSeRK^Sy&ht7;71sm>F0ES%nl09od8f6WNst zMT{CJF62;l+IUbj=;8+zR; z@QBE$5`HGdRHf`Rr zb=&qGJ9iyAeB|h{<0np@x^(%<)oa&p+`RSh(c>pipFMx^^3}&rpTB(l_Wj4tUm$-m zGBAUE1tK6mL-Us)0}~??3kx#~JIG&*OywYt3$m~(8nOvF2C^p>3M&~ka)>xhT)6Qd zr?PR-2hpUWi(FzVCJ$9Vg1iRy8F3zKBFkrRk0JbZi-Cukk%38&S&+e=;rlL)MQb)J z>e67K5oVIC`|vAR^V8plUzUY;cmFf~w@b=BVNsX%j0MElxaRkNhOe*xz5i;uF!K9< zhS%5s-hY+t6)$A6^y8wnB7w2)34yL63`Af?p8GYmT2_RpikGPk4EQl|xvNO{rYc5PkpNm_79&tx1wZ|L_@&Q! zaq&Ote@jgI4!Vkj8#)qWJ0hB#HoyPRaDDyn{a3C{*{71}{n#~1W6`d@1H{DpqAm?4 z$$dY5g_Np*jB{EwgJ_kUWE1&z9}Df=vbEn4_d*{@4ueaaW6E{z4W z#vG}jxLSVtd;6uCaeDec&i{ImDcjVgu`a0zYCB)~TjT!>=PT{sZ+5?x{-0sqr~3P8 zix%yhd?V;z=y$n#o86QCF023fUj6U4jNd;089v|h|NZ7lNZ=dIdad8h^I}idUAF)8 zee%EGCbdvySy~$3MD|auJ$PR4$^Ojw|Ef>^XQ-WJ2UTV&DpJiAe`?=@=hKVgx19ga zVEg1h!@jffaD`o6wypY4;|q#U@6?Aos2`!y)m84$^iS(|SU$Zo9jnv(6TN?i-;w$B z&imWU{|wLf%>RCKWm5UXqBm!XdnV6X9j5TR{dL6krG6&21HGS=B&MoIs{LnJEp*XU z^!dC4*dh{(80lq(=-C>FT{|{?a9doMZL`5=UffNkqBT1wIv$kyqP8pQsh!Gyh7*k| z|1;dy|0^}6cf(1m3;w(QoEQ1e(6ac5{1@3P{~12$`F~vePpDs@{z7^1e})4)?T>9* zvC?kyt1b2uYfoGyq;i4XYUiKgcY5Q0c-c7qXK=jh|DS>B$$thZ`-d7I+5fVa+W+C6 z{3lzhi+QLTxPIyCr(VC-t&}s%_Y(POsWk1kzQdv}&3Oh`A`nzDFqU2Z8~C50YWWIX z`>XQbS5-Y&`*HKlL-nlnu})nYYx*W&D~?vKwSWB7UjAvo6=?Qet65X{)z$ft+gy>r zxRWnfMFJgYjRnB@*?;z_qqR6RINYr^Iw<7O1l%d%5CjeyM6}kTfMBG-|Dx#{RNFhU5gKK zX)J2MhV^dMud;t1@|AV@uf_kq`p1awGpn}nf3W`bia=M6b(Pet%m3_x zKmH3{s^4d~VnvO|+vGbp> zRl)pY@z&_l; zL;_ulMPN1+iv+sXdOpJl9#{zjj@uX+tCs%P2Qyna*jpTS{KmsURqt_(kn8(&FX8tYfu z;B5DT<95~8B>o+BTieus_`Ez+FIgYUnX#zr)Af4q1NBu~; z{QJ#6>pV0Tb!|R_E!%UUwnnC2y^b8?OY&R)+1Q3ZtNG91DiSh31(sUTY04Q zhE{n5Q|{jM&-!~TRc|bGjsJIY{_l_f(*J0{8Q;F_pIQ6xq~1|3t^W+)eEu`s@BeK7 z&>g|rzVy%O_k5mZ8aRAlyI22Nd_|G>MyEB7-}$dChncL&yZd%cU&!S1qP%o{xiIt{;${T)obCSw``B?e^`Ci=;|ZSWjD7jRna)STIPZ91-UD|8_sH7$lvwn z{OlL&9sV=4IzHn6`RB6z`#1CdxXOQ6VdMCp!G2cy-@lXpGjy!9+x$Ajes1lH&%d-U z*gp)YX#Q7ka{l*k_5Tcl?tirS1?tZ~oB5xiKIK2dBMttw0sl(BFFsG$*adc5oqt}x z*KfaQFY`Z9_73~!pMCx_?B7)XQKbIRf{*Nf{$}fNQp2yS{Lka7Z2on9;r|gL zr|_R){#oOH|4#mAI1>1ufvfq5{O6y`>hIsQ|Ix)vQ-`i!_4+89|KqNQ^h=ws=9)8S zl7H>|1%a+2=OxffAsB!77Un2T4D@CPV!)#{{r-Q3_3_{9SFY8p@whboSl5<7*HT%W zO?9*;JQ)=>v=}0_XoIOA#@!@b^$}Pc5l>v__y1>z+yCxALvU2kkBLk5dqp!Ab-l92 z8{HZvy zew@C!S{mIYo)Ph@8ktGz+E^^f> zg1fZy!7by89s3{Kzh1bcj7wwD&Mu-%M2aTS`St(y*5CcNY|GM*%3kqOS{GeKuatq~ z71n&lLkpb!e*Dtnm1>}9yY6e>@}uYR-*5H$Cs{=T!*Fz}MP<$x8Qz@~7-Dx@{d@2yliF?m@0LHwUj53V|LLFbpL72++>AK?h&M(KV!dXN&i0X=rUwz6y>qY-1{+-(M z&;Mxs%(||HP@~^&sxP%ycE3_6fBMJz&-`!Jq<^Z@{&V_EC_mI_s8bhr{EI(YKchZ1 z*?#iJ_-FBpRzN(M0d?tD8TU{BwErCbtu6Uye%F5nqralgP_v;ur7^ti#HIHXh3R6@2j@G{%1S?!@p8fc&uON{}b_R{h1dB|1<0f{?8!v z=gcjQMf+y_CU-8Ax!CPLL%;cdhIQ>fxh|~vEB(*r{=Z%FPqdx(Ih*`vsGfiNlY1|C z`jS;7Fs||(dIARVhsK~H@(?5j8xgZ~pKlfAz5b`#{^6fjS+Lk%C;wyP*Z7$ioBuOZ zEdQtabLN&n*IL8xiP#!8M2N9HL(ajGzs&zs?*Ch2eiGGq?{`=0q_(?W7;E6JgMO~lI6=3UE!TLRe zdu9(`Ag~T(mvzND$Lul&p^68!Sy5UmPfyB(%^!KKsw`{p8}4 zlmAuj|NALBtm~`&R{Nbl{xck~z5n+8uW}H}XwOXh=d=Ga+*j*|X;>Sj@$bsN=a2t0 zFxYS3|MEVFaq3gbe};Ma{~2m#*}ybx%~)!Bbw{$2fV?mznvJlI?Ya~S`qy9PmcF~urI=E@jND4)0ZM(8it6v29C>F`_E zr@YO+{Yn0s-NxUyqy95|0Ifw*z?wrwJ60I0*hueKLCadK0ZfG4@Sf$t+BJeTB?h#M zMByOauw6re(ZxY-6%1r|Xa3sPV!zn>?Ut8J+GFtc&PIlar%gR6jaT}_0>Me}?Dhga0$^5B$&Yv84Xc{SW^c{``DZ|NWKy zKfOIcU9;!?XZU8z_x{6w2IX7-86M{UsQ*{<)&BQa`Tq>!?tgUuGw}asI6rUse}=ln z{}~>=KB}?k;opAe-GA&)eB1vc`k(xNhWWPs|NgrFXE=KCKLdCD;r|SupRfGSaDT;r zh9A2=dTH#aPrmT(KLh_K+x5gaNKgL87wi3p|MX}7XK3C3@IS-P&sY94yuVWaCszK$ z_CNOj8SJX&|NYhfpP_rb$fEA{fpdK63(-$YYr}nw&z>C#xhy$!NZ)DgOH^g3&P@4R!uKeT_n5(#uoDZ$##9j&COP)VWB|4#Wa`9Fga+w%Vm zIs3n41(_fE&mbqa`mH|BrT9m)rfQ^?%;KPYeH}1nP|}5S#g*pVu!&h0U5(Vk zO6@GoUx6k@&frQoqigOE1LLD>?r6F_39>aQ$b&PF(I(hz`*;7R@NI`Ji>7DSm;4LK z&3`xl)XpxUuGqDIFaHeOx_?)_7K4{&8A!3)wSO=FtYFkwdh7mO`>E2=zn6bnz=Eu= z0joAgmQt1gCfHybh^G30o8L!|=bF+RzA~HK-+cZ*gH6hRhJC(ByM-r;it68d{-0r< zQT=_jeuR?9XM%x2f9KWze60TWQ^s$b{|ujx`Tu@$fh8nlfBO7?6(>Qn7q07n>I?U4F{BPw`Tq=??$n>(v;X_S z1soYG_nj{M&tUiIKf`^t{y{6w`4+P4Aa(7Y(}KJr4bOMYntgIQWO@p*jY!hf9x9me zDHS&DbmApeVQ}=Q#lxn$AZBGSP6_@qvmQ3D1(gNIEZjUBn0x~ca}h&B>67*Ue6Iif z+C$@7%O7K-G--2&n5~17nCD5uCocLtji(i*tPWpTC>zdW|d~rQuPfP+`E7T6c zmSRwCCpfdy;lIxHd;hXVt=cC4O8W2n7g|?~uKt*LJp6H@t4LTHY(WTHtw>+b^mqMhQ=@+reM#7^ zxTs6}pc#Dd>pWt8Gi3{BrNm&d5W3+%L%{v_^^Reyr>#G7&T79jm&T&a#xN_5Uo7gn z@e$N&K_5-S%HNhPwJm!$>$iRGe)6ELoA=?tYAo15?{te!L zpZqyn^4Wdo_49I1qtL`kYxjW)g8^gCX8IX`I6 z*?a6eN6B1%gf&gm(yW>KcjcpMLCI|E)cl91FE-t2>(W@y0$Ra~ak2=mUIRYy^NYH) zlaIqPGeP?1nyj1a-iY7&DQi);&{ZV766?-uoYe^pj$on4E<)8Cp~OZ&ezdZ>Hs9lv zM8dVw;3-nEjd|gVmVKIj<>FKQmFv}CP2FARHs?fJ*X&dA!R}M`E$d$u_Vy>cks@5p zVvrKYGaVT#_bmNtQtACF`xAs$yu3Y^ z*W^%_#+qZqCY|()uA&V{A1u!`ChS% zMO|O}9)J{$6ujzc+T^VN3?+4;C%YDEZF~A6B)02IXz%L37k>tBt>0y@_2gHp-|JsL zMbDuM+bs;<^>3f!FcIyk(vCja{jYNQ-%lE!lKwNy%lyw!({=e(@w-0$L}j(EYY0J` z8T+5ls=u$)zw-R9!6pYQKSJ*L#D6|(|9+xhXsAReLp9f>KBld!bSXaOP%A5V`XU25QJ{#}sK5Jjw!bSaHp&3O=`KGnJa#_Em+Pdua z2XFefFY!d@o~vI>Dp$YAlG6C~*YEVNpY2KrvlhSpYOVkJnGA78HNU-BU;3Xx(f#(K zf3N>JZGN%VZl~Re2j4!K*xeQX9{)+Cc9;IU{ZF**x>QgTGlu?IQ#P)qgf;wsS zJEpAuAfnRZKf@z^|Ia3Epe-YUxK2w$8;KbVn*fJOUy==!{enS6oS*%V?#qA9DuTvm dkH8MaLf{lu7Ha?i diff --git a/libs/ultrahdr/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12 deleted file mode 100644 index 7b2fc71bc0..0000000000 --- a/libs/ultrahdr/tests/data/minnie-318x240.yu12 +++ /dev/null @@ -1,1930 +0,0 @@ -ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}vrswqnpsqqklspmjmnlknw{’™›œ›š›››™›ššœœšœžŸ¡¢¤§«¯³µ¸ºº¼¼»º¹µ²­¥ž—”’”¦®´µ·¸¹º»»¼»¼¼½¾¾¾½¼º¹¸¹º»¼»½ÀÂÃÆÊËÊÊÍÍÌÌÉžºµ±­©©­´¶¶¶³²²³°¬©­²³¯©¢¡¨±´·µµµ³­›…„•¬¸º¹··³¨ ¦¯±£ÙÙ×ÕÒÑÏÎÉÇÇÈÅ¿¿ÁÄÂÀÀļ·´±­¬«®±²°­°§Ž›­®¯¯°²²²µ¹»½¼¼¼º¸¸¹»½¾¾ÁÅÇÆÉÍÏÐÏÎÎÌËÌÌËËËËÌÊÇÇÉÊËÌÌÌÊÉÈÈÆÇÇÇÈÇÇÆÆÆÆÆÆÄÃÃÃÂÂÁ¿½¼¼½½¾»¹¹¹¹¹·¶¶µ´µ´²±²±¯¯­­®­¨¤¢¥¤f96999:7464336753/01,..2344N“žœœššœœœœžž¡£¦«®°³µ¸»¼½½¼¼¹´®¨¡›•”–𤫳¶¶·¸º¼½½¾¼½½¾¾¾¿¿½»¹¹ººº»¼¾ÀÂÅÈËÌËÎÏÐÎÊÆÄ½¸µ±¬¨§¯·¶µ´²±²²°­©®²³®§¥¥«°³¶¶µ´¯¨“‚ˆ¡³¶¸···³¨¡§°´²ÚÚØÖÓÒÐÐÍÊÇÇŽ¾½¿ÁÁÁÃÄÁ»µ²¯®ª«°°¬¥£¬“¨­®¯°±°±µº½¼¼¼¼»¹¸¹º¼½¿ÁÅÈÉÌÎÐÐÎÍÍÊÊÌÍÍÌÌÌËÉÈÈÈÊËÌËËËÊÊÉÈÈÈÇÆÅÆÆÆÆÆÇÆÆÄÄÃÂÂÁ¿½¼½½½¾¼¹ºº¹¸·¶µµ´´µ´²±±¯¯­­­¬¬§¤ªr'Rouwwvzy{zxyzyxtsvurkgce_F U¢žœ›››œœ››Ÿ ¡£¨«°±³µ¸»½¿¾¾½º·°«£›–••˜ ¨®´¶¶¹»¼½½¾¾¾¾¾¾¾¾¿¿¼»º¹¹º»¼½¿ÁÄÇÊÍÍÍÑÒÏËÉÄÁ»·´±­§¨²·¶´³³³²²¯«©®²³­¨¤¦°³··¶µ³®£‰–®µµ¶¸¸¸²¤ §±¶·Ù×ÙØÔÑÐÍÎËÈÅÄÃÀ¿¼º¼½ÀÁÂĺµ²¯®­­®©™”ž£–£ª­¯°°±°²¹½½¼»¼º¹¸¹¹»¼¿ÁÄÈÊÌÎÑÑÏÍÌÊÊËÌÌÌËÌÊÉÉÈÈÊËËÊÊËÊÊÈÉÉÈÇÆÆÆÅÆÅÄÆÃÄÄÁÁÂÂÁ¿½»¼½¾½»¼»º¹·¶¶··´´µµ´²±°°®­¯®«ª¥¤@: ¬ª¬©ª¨§§©ª§©¨§©¦£¦¦£•¥£¤•;:—Ÿžœ™š››œ›œœŸ£¥§«®±´·»½¾¿À¿½»¸³¬§Ÿ™•”–ž¦­°³¶¹»½¾½¾½¾¾¾¿¿¿¿¿½¼»º»º»¼¼½ÁÂÇÊÊÌÏÑÑÐÏÌȾ¹´¯¯¬§«´¶¶´µ¶³²±¯ª¨­³²«¨¢©¶¹ºº·¶²¬™~‹¢°±²µ·¹¶®£ ¦±··Û×Ö×ÕÒÐÌÍÊÇÇÅÁ¿¾º¸·¹¼¿Á¿¹µ²¯®®°«—‰‘œš¡©ª­®²²°³º¼¼¼»»º¹¹¸¸¹¼¿ÀÄÇÈÌÎÐÑÏÎÍËÉÊËËÍÍËËÊÉÈÈËËÊÊËËÊÉÊËËÉÇÆÈÇÇÆÄÄÅÄÄÄÃÃÂÂÁ¿½½½¾¾½»º»»¹¹¸¸¹º·µ¸·¶¶³²³°°°°®­ª 5M¦¡¢¡¢¤¤¦¥£££¤¤¢  ¡ £}>Ž¡ž¥I1‘Ÿœš™™›ššš›œ›œ £¦«¯²´·º»¾¿¿¿¾½»µ®©£›˜–•›£©¯³¶·¹½¾¿¿¿¿½½¾¾¿¿À¿¾½»º¼»¼¼½¿ÁÃÇÊÌÎÏÑÑÐÎÊÆÁ¼¶±«ª¨¨­³µµ´´´´³²¯©§­±®«¦£­´¸º¹¶´°¤‰€’§«­°³¶·²­£ §±¸¹ÛÙ×ÓÑÓÏËÊÈÄÄÃÀÁ¿¼º¶´µ¸½ÀÃÿ¹´²°­¬«£ˆˆ”›Ÿ§ª«¬°±¯°º½½½½»»º¹·¸º¼ÀÀÃÆÈËÎÐÐÐÏÍÌÊËÌÌÍÌËËÊÉÇÈÊËÊÊÉÊÊËËÉÈÈÈÉÈÇÆÇÇÆÄÃÄÃÅÄÃÂÂÀ¾¾ÀÀÀ¿½»»¼¼»ºº¹¹¹¸¸¸¸¸µµµ³³³²°¯¬5T§¡£¢¤¥¦¦¦¦¥¥¥¦¤¢¢¡ ¢T1s¥Ÿ¢F)sš›—–————˜™››ž £¦ª¯³¶¸º½¿¿¿¾¾½»¹±­¦Ÿ™•–›£¨®³µ·¹»½¾À¿¾¾¾½½¾¿ÀÀ¾¾¼ºº»»½¾¿ÀÂÄÈÊÍÏÏÐÑÏÌÈÄ¿º´­©§¦§°µ´´³´³³³²¯¨©±±­¨¢¥°¶¸ºº·²¬ƒ‰˜¤¨¬¯²µ¶²­¤¤§²»ºÚØØÕÐÏÎÌËÉÃÁÀ¿¿À¾º·²°²¶»¿Â¼¸´²®¨©ª„Œ”ž©©««®°±°¸»¼½½»»»¹¹¸»½¿ÂÄÆÆÊÏÑÑÑÐÍËÍÎÎÎÌËÊÈÊÊÇÆÉÊÌÌËËÊÊÉÈÇÈÈÇÇÇÇÈÉÈÅÄÄÄÆÄÃÄÂÁÀ¿¿¿ÀÀ¾¾½½½¼»¼»¹¸¹º¹··¶µ´´µ´²²°­™2Z©¤¦¥¦¥¦¥¦¥¥¤¥¤¤¤£¢£‡HeP¡ ¡@&-Hh‰—–’”—ššœ ¤¦©­°µº»½¿¿ÀÀ¿¿¾»¶®©¡œ˜•™¢ª®²¶·ºº¼¾¾¾½½½½»½¿¿À¿¿¾»¼»º¼½¿¿ÂÄÆÊËÎÏÐÒÒÎËÇ»·°«¨¦¥¦­²³´´´´µ´²­¦©¯¯­¥ ¥·¹»»º·°§Š™¥©¬°²µµ´«¡¤©´½»ØÕÖÕÑÌÊÊÉÇÄÁ½»¾¿¿»¸³¯­¯´¹½Á¿»¶²­©§­£…ˆŽ—¥§©«¬®­®·¼¾»¹»¼»ººº»½¿ÂÄÆÇÍÑÑÓÒÏÍÌÌËÌÍÌËËËËÊÈÈÉÉËÌÍÌÊÊÉÈÈÇÆÄÅÅÆÉÉÇÅÄÄÆÅÂÂÃÃÁÀ¿¾¾ÀÀ¿¾½¾¾½»»º¹¸ºº¹¸¸·¶¶¶µµ´³±°–0b­¥¦¦§¥¥¦¦¥¥¤¥¤¤£££¨_gA‘¡š8$A,&6W†—›š›ž ¤§ª®²µ¹»½¿¿ÀÀÀ¾½¼·±©£ž™—™ž¦«¯²¶¸¼½¿¿½»»¼½»»½¿¿¿¿½½»»ººº»½ÀÃÆÉÌÍÐÑÑÒÒÏËÆÀ¹³­©¨¥¤¨¬°²³³´··¶±«¤¨­­©¢ž«¸½»¹·³® „ƒŸ¨«®¯°µ·²§Ÿ£«´»½ÐÌÎÐÒÎÊÇÆÆÅÁ¾¼¼¾¾½¹´°«¬®²·½¿½¶³®ª§¬°‰Œ” ¤¦¨ª©©ª¶¼¼º¹¹¹º»º»½¾ÁÂÅÈÊÍÐÐÒÑÎÌËËÌÌÍÍÌËËÊÈÇÆÆÈËËÊËÊÊÊÉÉÈÆÄÄÅÅÅÆÆÅÆÄÅÅÄÄÄÿ½½½¾¿ÀÁÀ¿½½½»»º¹·¹¹¹»º¹¹¸¶µ´³´³³•/l²§¦¦§§¨¨¨¨§¦¦¥¥¥¤¤—?S_8z¤”06‘}V5$.YšŸž¡¦ª¬®²µ¸¼¼¿ÁÁÂÁÀ¿½·³­§¢™™Ÿ¥¬°±´¸»½ÀÁÀ¾½¾¿¿¼½¿À¿¾¾¿½º¹¹¹¹»¾ÂÄÈÌÍÑÓÓÓÓÐÍÇž·±¬¨¦¤¥ª°²²²³µ··´¯¨£§««¤Ÿ¡±¹½»·µ±ª“‰•¢ª¬¬­²¶¶¯§ ¦­´»¼ÂÀÃÈÇËÊÇÅÄÁÁ¿½»»¼¼¹¶²­¬«¬±´º½º´°ª¦§±¨‡‹’¡£§©¨©«´»¼º»¼ºº¹º¼½¾ÁÂÆÆÊÎÑÑÑÐÎÊÊÍÏÍÌÎÍËÊÉÉÈÄÆÉËÌËËËÊÊÊÊÉÉÇÅÅÅÄÄÄÄÅÅÄÄÅÅÅÃÀ¾¼¼¼¾¿¿¾¿¿½½»¹¹¸·¸¹¹»ºº»¸·µ´³´³´”0xµª§¥¥§¨¨¨¦§§¥¤¦¦¤¥vXzvT^ ,C¢¤¡ˆ_;.2Mu•¥¥ª®²µ·»¼½ÁÂÂÃÂÀ¿»¶¯©£¡œš›£¨®²´¸»½¾ÀÀÀ¾½¾¾½¾¿ÁÂÁ¿À¿¾»º¹º»¾ÀÃÈÊÍÐÓÕÕÔÒÎÊÅÀ¹³®¨¦¥£§¬¯±±²²µ¶¶³®¨£§«¦Ÿ›¦µ»¼º·²­¢†ƒœ§­­­¯´µ´¯¤¡§­µ¼½¹µ³¼ÁÄÆÈÆÃÂÀ¾¾»ºººº¶³±¬©¨©®´¹¼¶±¬¨¥§±—‰™ ¢¦©¨©«³º¼½¼»»»ºº½½¾ÀÀÄÇÉÎÑÑÐÑÎÌËÌËÌÍÍËÊÉÉÈÉÆÆÈËÌÍÍËÊËÌËËÊÉÈÉÆÅÅÃÄÅÆÅÃÄÄÄÃÁ¿¾½¿¿À¿¿¿À½½¼ºº¸¸¹ºººº»»¸¶¶´´µµº“2‚¸®«¦¤¥¦¦¦¤¤£¡£§¥¤¤qЧ¦ŒW™ƒ'LŸ›¡¡ŽjI93Cjž­°µ¸»¼¾ÀÃÃÂÁÀ¿½¸³­¦¡ž››ž¦«°´¶º½¾¿À¿À¿½½¾½¾ÀÂÂÁ¿¾½¼º¹»¼¿ÂÄÇÌÎÐÓÕÔÔÓÏËǼµ°ª¥¢£¤©®²³´³µ¸¸¶³®¦¢¨ª¢™š«¸¼»¸´°ª˜€…“¢ª®®¯³²µµ¬¢¡¥¬¶¼¿¶´²±·¼¿ÅÆÄÂÁ¾»½¼º¹¸µ²²¯«§¦¨¬¯·¹´®¬§£«®Š˜ ¢¤¨ªªª³º¼¿¾¼»»»»¼½À¿ÀÁÅÈÍÏÐÐÐÍÌËÌÌËËËËÉÊÊÉÈÇÇÇÉËÍÍËÊÌËÊÌËÉÉÉÉÅÅÅÅÆÆÅÄÄÂÃÁÀÀ¿¿ÀÀÀÀ¿¼¾¿¿¿½¼ºº»ºººººº¸¶¶µµ´´¼ˆ3ˆº°­ª¦¦¦¥¦¤¢¡Ÿž£¤¤£ £Ÿ››ƒ$W šš› ¢žŠmR.T­¶¶»½¾ÁÃÄÃÂÁÀÀ»¶±«£Ÿœ›ž¤«¯³¶¸»½¿ÁÀÁÀÀ¾½¾¿ÀÂÂÃÂÀ¾¼¼¼»¾¿ÁÅÇÊÍÐÒÓÓÔÔÐÍÈÿ¹±«§¤¢¢¤¨¬°³¶¶¹¹·¶³¬¤£ª§¡™¡²·¹¹¸´°¦†ƒ‡˜¤«®¯³´µ¸´«¢£§­¸¾¿º²°¯±µº½ÂÃÃÁ¿»»»ºº¹¸³±¯¬§¥¤¥§¯³¶³¯«¦¨´ª‡”¢¥¦©«©ª±·»¾¾¾¼ºº¼»¾¿ÁÃÃÆÈÍÐÏÐÎÌËËÌÌËËÊÊÉÊÉÉÈÇÆÇÊÊÌÌÊÈÊÉÊËÌÊËÉÈÇÇÇÅÄÅÅÄÅÄÄÂÀÀ¿ÀÀÀÀÀ¿½½¿¿¾¿½º»»º¹¹¹¸¸¸··¸¶´²¼5Œ¹°®­«ª©§¥£¦x𣢤£¤£¡žœœž€ ^£œœ››i‡­¬¥X/˜º¹¼¿ÁÁÂÄÃÂÁÁ¾¹³®¨¢œ›¤ª¯²µ·º¼¾¿ÁÀÁÀÀ¼»¼¾¾ÀÁÀ¿¿½½½¼¼¾ÁÄÆÊËÎÐÒÔÔÔÑÎÊÆÂ¾·¯©£¢¤¥¦ª®°´·¸¸¹¸µ°«¥£¨¦œ™©´·¹¹·³ªš€‡œ¨¬­°´´¶·²¨ ¤©¯ºÀÀÁ´¯°°±µ»¾¿ÀÀ½»¹¸¹»º·¶³°«¨¤ ž ¤«²·µ­¨¦¬¸™Œž¢¥©«ª¨®¸»¼¾¾¼¹»»º»½ÀÃÅÇÊÍÎÍÎÏÍÊÉËÍÊÌÌÉÉÊÊÊÇÇÇÈÊËËËÊÈÉÉËËÌÍËÊÉÉÇÅÄÃÅÄÃÄÄÄ¿¾¿¿À¾¾¿¿¿¾¾¾¾½»¹ºº¸¹¸¸¹¸¶¶¶¶¶µ±¹s2’·°¯­¬«©¦¤¦—G#L—¥£¤¤¤¡Ÿžœt g¤›œœŸi1t¯¨³o0”¼º½¿ÀÁÁÁÁÁÀ¿º´°ª¦ œ¢©®±´·¹»¿¿ÀÁÀÁÁ¿»º»¼¼½¾¿¾½½¾¾¼»½ÁÄÆÊÍÐÓÓÕÖÒÎËÇþº²ª¥¢£¦¦¨¬°²¶¸¹¹¹¶´®ª¢ ¤ —°µ¹¹·¶°§Ž‡”¢«­­°³¶·¶°¤Ÿ¤¨°»ÁÁɺ±°±°²µº¼¾¿¿¼¸··¹¸¸¶µ²¯©¥žšš¡§±¸µ¬¨§¯°‘›¢¥§ª«¦¬¸¼¿¿¾½»»¹º»¼¾ÁÄÇÊÌÍÍÏÏËÉÈÈÊËËËÊÉËÊÉÈÆÈÉÉÊÌËÉÉÊÉÊËËËÊÊÊÊÈÆÆÄÄÄÄÄľ¾À¿¿¿¿ÀÀ¿¾¼¾¾¼ººº¹¹¹¶¶¹¹·µµ¶µ³±¸j.—´°¯®­«¨¥¥›B!)#JŸ¤¥¥£ žŸž n"r¤£zOXw±¬²Z;¬½»½¿ÀÂÁÁÂÁ¿»·²¬§¢Ÿœž¤¬¯³··º½ÀÀÀÀÁÁÁ¼»¼¼»½½½½½½½½½¼¼¼ÀÄÇÊÍÑÔÔÕÓÏÊÉÄÀ¼µ®¥¡¡£¦§©«¯²µ¹º»¸¶³¬¥ Ÿ œ™¨¶¹¹¶´±¬‚‚Œ™¥©¬®²¶·¹´­£ £«´¾À¿Ïµ²±°±´·¹º¾¿½¹¸·µ´¶¶µ³²®¨¡™—˜š£¯¹¹¯¬­¶¢•££¦ªª¨¬¸¼½¾½¼½º¹»¼½¾ÁÄÇËÌÎÐÑÏÌÉÊÉÊËÊËËËÉÈÉÈÇÈÉÊËÍÌÉÊÊÉÊÉÉÉÈÈÉÉÊÉÇÅÄÃÄÄÃÁ¿¾¾¾¿ÀÁÀ¿À¿¿¾¾¼»»»ºº¹¹·¸»»·¶¶µ³²±¶b/𴝱°°­©§¥M'()%dª¤¤£ ¡¡Ÿ¢j"}¥ž¤‹EŒlwµ²¥?`½½¿ÀÂÃÂÁÀ¿¼¸´­©¦ žž¢¨¬²µ¸¹»¾ÁÂÁÀÀÀ¾»»½¾¼½½½½¼¼¼»»º½½ÀÃÈÌÎÒÓÔÓÐÍÈÅÿ¹±¬¥¡ £¥¦©¬°´¶¹º¹¹¶´¯¨Ÿœœ˜°¹»¹·´¯¥Œ‚ˆ‘ž¤©¬¯´¹¸¸´©¢Ÿ¤®·¾Á¾ÏȺµ´²´µµµ¸¹»»º¸¶µ´³³µ²³°«¦ ™–““›¡¨µ»·³¶¶› ¢¥¨©¦ªµº¼¾½»»º¹º½¿ÂÃÅÉÍÎÎÐÐÎÌÊÊÊÉÉËÌÍÍËÉÊÈÈÈÇÉËÌËËËËÌËÊÉÈÈÉÊÈÈÉÇÆÃÁÁÂÁÁ¾½¾¾¿ÀÁÁÁÁÀÀ¾½¼»¹¸¸¹¹¹º»ºº·µ¶µ³³³·^4³®°°°¯ª¬u%&&'&/•¥£¤££ Ÿ¥e'…¥¦•IEzXy´·|5•ļ¿ÀÂÃÃÃÂÀ¾º´®©¤¡ŸŸ¢§¬²¶¸»¼½ÀÃÄÂÀÀ¿½º»½¾¾¿¾½½¼¼¼¼»»½¿¿ÃÊÎÐÓÓÒÐÍÈÅÃÀº³®¨¥¡¡£¤¥©¬±¸º»»¹¹¶³­¥›š™£³º»¹·³«œ‚‚‹–¢¦¨­±¶·¹¸±¤¢Ÿ¥°¹¿Á¿ÐÍÀ·¶¶¶´µ¶¶·¸¹··µ´µ³²²³´³°«¥Ž’–›¥³½¿¾¹¦› £¤§§©´¹»¼¾¼»¼¼»¼¿ÃÄÅÊÍÎÏÐÐÏÍËËÊÉÉËÍÎÍËÊÊÈÈÈÈÇÈÉÊÌÌËÌËËËÊÈÉÉÈÇÇÇÅÄÂÂÁÁÀ¾¾¾ÀÁÀÀ¿¿ÀÀÁÀÀ¿¼¹¹ººººº»º¸¶´¶µ´´´¸[9¡±®¯¯°®«¥B%)'&'++y¬¤¥¤£¢Ÿ¥^-§¦dm…g@}¸µPJ¹ÁÀÁÂÃÃÃÄÂÁ½µ°ª¦¢¡Ÿ¡¥ª®´·º¼¾ÀÃÄÄÃÁÁ¿¼»¼¾¿¿ÀÀ¾½½¾½½½»»½ÁÅËÏÑÔÔÑÏÊÆÃ¿»´°«¥¢¡¢¤§§¨«²·ºººº¸µ²«£›™—™©¶¹º¸³¬¦“…™¤§ª¯³¶¸º·¯¦¡¢©³º¿ÀÀÑÏĸ··¸·¶µ´´¶¶µ³³³²³³²²²´³¯¦¢”‰‰Š‘•ž­½Æ¿«˜ž¡£¥¤§²·º»½¾¼½½¼½¿ÂÅÇÉÌÍÏÐÑÎËËÊÉÊÊËÌÌÊÊÊÊÊËÊÊÉÈÉËÍÍËËÌËÌËÉÈÉÊÈÈÇÅÄÂÂÁÁÁÀ¿¿ÂÃÂÂÁÁÁ¿ÁÁÁ¿»¹º¼»»º¹¹·µ´¶·¶´´´¶X>£¯®®®¯¯­Ÿ9,+)))0.v¬¦§§¥£¢§Y2–¨¢—®±µq‚½Ÿ8}ÇÂÁÂÃÄÄÄÃÁ½¸±­©¦¢ Ÿ¥©¬°³·¹»¿ÃÃÅÃÂÀ¾½»»½¾¿ÁÀ¿¾¼¾¿¿½½¼¾¿ÄÉÍÏÒÓÒÐÎÈÄÁ¼¶±¬§£ ¡¤¦§§©ª²·¸º¼¹·´¯ª¢™–“ž±ºº¹µ±«œ„‚‡’ž¦§¬²¶¶¸¸µ¬¤œ£­µ¼ÀÀ¾ÒÒʺ¹¸»¹·´³³³²²¯¯±³³´³±°±³²¬§ –Іˆ‰“•Ÿ­´«—™ ££¢¥¯¶¹»½½½½¾¿ÀÀÁÅÈÉÊÌÎÎÏÌËÊËÊËËËÊÊÈÉÈÉÊËÌÊÈÇÉËÍÎÍËÌËËÊÉÇÇÉÉÈÇÅÅÅÃÁÁÂÀ¾¿ÂÄÃÃÂÁÀÁÁÁÁ¼»ºº»½»¹¸¸µµ···¶´µ¶µQB©¯­«¬­­¬ªZ04*+57F—§¦§¦¥¥¤§T6­ª®­­³”›½lA®ÃÀÀÁÂÃÃÂÁ¾¹´­¨¤¢ Ÿ¢¦«¯²´¶¹¼¿ÂÄÄÃÀ¿¼¼»¼½½¾¾½½¼»¼½¿½¼½ÀÄÇÌÏÑÑÐÏÎËÈþ¹³®©¥¡ ¢¤¦¦§ª­³·º»º¸¶²®¨¡™“•§¶»»¹³­¨’€†‹š¢§«°µ··¹¸²§š¢°¹¾¿À½ÏÒξ¸º»º¶µ´³²³±­¬®±±³²°¯®®±¯¬©Ÿ‘†„‡ŠŒ•›—–ž££¢£­¶º»¾¼¼¼¼¿ÀÀÂÄÈÊËËÍÎÎÌÉÇÉÊÉÊÉÊÊÉÉÊÊÉÊÌÌÈÈÉÊÌËÍÍËÊËÊÈÈÈÇÉÈÆÅÅÄÂÂÁÂÁ¿ÀÀÃÃÃÂÁÁÀ¿¾¿¿¿¾½½½¼ºº¹··¸¹¹¸¶¶µ²LI®®¬¬­­­ª®œme=1bm¥£¦©¨¦¤¤¦O<¤¯¯­¯±²³·ªAjÆ¿ÀÀÁÂÂÂÁ¿¼·±«§¢¡ŸŸ¡§­°³¶¸º½ÀÁÂÂÀ½½»ºº¼¾¾½»º¹ºº»»¼½½¾ÂÅÉÌÏÑÒÏÍÍÊǽ¶¯ª¦¢ ¡¢¤¨¨¨«°µ¹º»º¸µ±¬¦ ˜“¯¸¸¸µ±©Ÿ…‚Š–Ÿ¦©­±¶··¸µ©Ÿ˜ž¦°·¾Á¾ÏÐÏź¼»ºº·¶³²²°«©ª­®®¯®­«¬®°®¬§‹‚ƒ…„‡”–‘œž¡ ¡¨³¸»¼»¼½½¿¿¿ÁÅÇËÍÎÎÍÍÊÊÈÉÉÊËÊÉÉÊËÊÊÊÊÌÌÊÉÉÊËËÌÍÌÊÊÊÉÈÈÈÉÈÆÃÂÁÁÁÁÁÀÀÁÀÂÁÁÁÀÁÁ¿¾¿ÀÁ¿½½½½»»»º¹·¹¹¹·µ²¯FO°®­®°¯¯­¬°³¤H7–­£ ¥¥§§¥£¦ªLB«±±°±²³µ½‡:›Ä¾ÂÃÂÃÄþºµ°ª¦¢¡ ¢¦«°³¶·º¼¿ÀÀÁ¿½¼¼¹·¹»»º»¸·¶¸¹º»»¼½¿ÄÇÊÌÎÐÑÐÍËÈÄÀ¸±­¨£ŸŸ¡¢¥¨§¨¬´¸ºº¼»¸µ±¬¦ž””¦µ¹·µ²¬¤“‚…–¡¨¬¯µ···µ®¤œš ¨®¹¾Â¼ÎÒÐȼ¼»ºº¹·¶µ³®©¤¢¤©«««ªªª­¯¯­ª£—‡€‚ƒƒ‡Œ‘Œ”›œž¨²·»½¼»»½¿¿ÀÂÅÇËÎÐÐÏÍËËËËÊÊÊÉÉÉÉËÊÉÈÊÍÍËÉÊÊÉÊÌÍÌÌÉÉÉÈÈÇÈÉÅÃÂÁÁÁÁÀ¿¿¿À¿¿¿¿ÀÁÁÀ¿¿¿¾½¼½½½¼¼¼»¼·µµ´³²±§;Q²¬­®¯­¬¬­¬®ˆ>8}§ £¤¦¨¥¥§ª®IC¯²²±²´¶¸¸XV½À¿ÂÂÃÃÅÂÀ¼µ°­¨¥¢¢¡¤ª­±´·¹¼¾¿¿ÀÀ¿¾¾»ººº»ºº¹¸¹··¹ºº¹»»¾ÂÆÊÍÏÑÐÐÍÉÄÁ»µ±«§¡žž ¡£¦§¨¬³¶¹¹»¹¸¶²«¤ž–¬¶¸¶³°¨ž‡…†š ¦¬´¹¹·¶³¬ ššŸ©±º¿À¿¼ÏÏÏÊ¿»¼»¹¸¹·µ²­© Ÿ £§©¨¨¨«¬®°±¬§ ’‚}|€„‰ˆ™™™›§²·»½½½¼½¿ÀÂÃÄÇÈÊÍÑÏÌÌÌÌÌÍÍÌÊÉÉÊÉÉÉÆÈÊËËÈÊÊÊËËÍÌÌËÊÉÈÈÈÉÈÆÄÃÂÂÁÂÁÀ¿¿¾½¾¾¿¿ÁÁÀ¿¿¿¾½½½¿¿¿À¾½¼¸·¶´²±±¥7W¶®¯°±¯­­®®¯—|z¦¢£¥¦§¥¨«®¯GJ³²³³µ¶¶¼›;†ÈÀÂÃÅÄÃÿ»²¬¨¥¤£¢¤©¬¯´·¹º½¾¾¿À¿¾¿¿¼¼»¼¼¼º¹¸¹¹¹º»¼ºº½¿ÃÇÊÍÐÑÑÎËÅÁ½µ°­¨¥ Ÿ ¢¢¢¤¦©®´¶¹ººº¸µ°©£›¡´¸¹µ±­¥‘„Д𠦱·¸¹¸¶±¦ž››¡«³¼¿¿¾¼ÎÍÍ˺¼¼¼¸¶µµ²¬¨¢  Ÿ ¡¡ ¤¨«®°®¬© {|~€†Œˆ‰–™–—¢°¶¹¼¼¼¼¼½¿ÁÃÄÆÈÊËÍÏÍËÌÌÌÍËËËÈÈÈÈÇÈÇÉÊÉÉÉÊÉÉÊÌÍËÊËÊÈÉÊÊÊÉÈÄÄÃÃÃÃÂÀÀÀÁÀÁÁ¿¾ÀÀÁÀ¾ÀÁÀ¿¿¾¾¿À¾¾¼¹¹¸µ´´µ¤5c¹®°±²¯­®°°°²µ²ª¥¥¦§©¨§©¬±°EP¶²²³¶¶·¿oE³ÅÂÃÄÆÆÅľ·¯ª¥£¡£¥¦«°²µ·¹¼¾¿ÀÁ¿¾¿¾¾»»»»»»¹¸¹º¹º½¾½¾ÀÂÅÇÈËÎÑÒÏÌÉÄ¿º±¬©¦¤ ŸŸ¡¢¢£¤©¯³·¸¸¸·µ³­§¡™˜«´¶´²®¦œƒŒ—¤«·¸¹·¸´ª¢™šž¥®¹¾¿¾¾¹ÑÐÒ̹¼¼¼¹·µ´°«©¥¡¡—•——›ž£¦ª­®°±¬¢‹~zz|}€ˆ‡…“”““Ÿ®µ¹º»½½¼»¼¿ÁÂÅÈÊÌÍÍÌËÌÌÌÌËÌËÈÇÈÇÆÈÇÉÊÊÊÊÉÈÉÈÉÊÊÉÉÉÉÉÉÊÊÉÈÇÄÄÄÄÃÂÀ¿ÀÁÂÀ¿¿¾ÀÀ¿¾¿¿ÀÀÁ¿½¾¿¾½¾»¹¹¸¶´µ¶ 6p¼ªª³²¯¬­°°°²¯­©§¨©©©¨¦§«²¯AWº³²´µµº²DlÇÁÂÃÅÇÆÅÃÀ»´¯ª¦¥¤¤§©­±´¶¸¹¼¾¿ÀÀ¿¾¿¾»¸¹º»»ººº»¼¼¼¿¿¿ÂÆÇÈÉÉÌÐÔÒÏÌÇÁ»µ°«§£¡Ÿž   ¢¢¦«°³µ·¸¸µ´²­§ šž®²²±±©¢{‚„Œ”¦²·¸¸¸¶±§žšœŸ¨µ½¿¿½»¸×ÔÓÏȼ¼»··¶´²°¬©¥¢¡œ”Ž”™œ¡£¦©¯°¯® ‹yyy{|~„’ž¬±µ¹º½½¼¼¾¿ÀÂÅÈÊÏÏÎÍËËËËÌÌÍËÉÈÇÆÆÆÈÉÉÉÉÉÉÊÉÈÉËÌËÊÉÉÉÉÉÊÉÇÇÆÇÆÅÅÃÁÀ¿ÁÀÀ¿¾¿ÁÁÀÀÀÀÀ¿À¿¿ÀÀ¿¿¿º¹¸¸·¶¶¹š2yÁ™m¹³²}£±¯°°°®«¬«¬ª©¨§ª¯³­<^¸²²µ¶¶Â8¢ÈÄÄÆÇÇÅÃÁ½¶°¬©§§§§«¯²¶¹ºº¼¿ÀÀÀ¾¾½¾¼»¹º»»»»¼¼½¾¾¿ÂÁÂÅÇÈÊÌÍÐÑÒÐËǽ·±®«¨¤¡ŸŸ Ÿ ¢¤§¬°³´··¶´²°¬§¡œ¥®°°°¬¥™…€ƒ…Ž˜¢®¶¹¸¸ºµ¬¤œœ¡ª¸¿¿À¿»¶ÚÖÓÐʽº¼ºµ´²°®­©¦£¢œ•Љ‹‹“•–ž £§©¬ª§œ†{yz{z~|‡Žª¯´¹¹¼¼½¾¾ÀÁÁÅÊÍÐÒÑÎÌËËÊÊÊÉÉÇÇÆÅÆÇÇÉÈÉÉÈÉÊÉÉÊËÌËÊÉÉÈÈÉÉÈÇÆÆÆÆÆÅÂÁÁÀÀ¿¿À¿ÀÁÀÀÀ¿ÁÀÀÀ¿¿¿ÀÂÁ¿»º¹º¹¸¸¼”.|Á²Ro‚wc²±±²²°®¯¯®®«¬©§®³¶ª;c¶±³·¹½Á]ZÈÅÆÇÈÇÆÂÀ¾º³­©©¨§§«®²¶·¼»¼¿ÁÀÀ¿¾¾½¼»»»»¼¼¼¼¼½¾½¾ÂÄÃÃÅÇÉËÌÎÎÏÏÌÇÁ½·±®«©§¢ž ¥¥¨­±´´´¶µ³²¯«¨¡¦­®®¬¦¢€„†Œ•Ÿ©²¸¹¹¹·¯¥Ÿœ›œ¤®¹¾ÀÀÀ»´ÜÙÓÎÈÀ¹º¹·´±¯­«©¦¤¤ ˜‹ˆ‡ŠŽ“——›Ÿ¢¡¤¢•}yyyxywŠŒŽ™©¯´·¸¹º½¿ÀÂÂÂÆÉÍÐÑÑÏÎÌËÌÊÉÉÈÇÆÆÇÇÈÇÉÈÉÉÊÊÊÉÉÊÌÌËÊÊÊÉÉÉÉÈÇÅÅÅÆÆÅÂÁÂÂÁÁÁÁÂÂÂÁÀÀÂÂÁ¿¿¿ÀÁÁÁÁ¿»»»»ºº¸½“.ƒ¿¼iPsBй²²³²±°±±¯­««©«±³µ¦7f¸²¶¹¼Ä¨A“ÌÄÆÆÆÅÄÁ¾º¶°©§§¨©ª¯±´µ·»»¼¾¾¿Á¿¾¾¼»ºº¹»¼¼¼»º»¼½¾ÁÃÃÄÆÇÈÊÊÌÍÎÎËÆÀ»µ°¬©§£ œ›œž¢¥¨¬¯²³³³´³³±­«¦¡Ÿ§«¬­©£™…€ƒŠ‘›¦¯µ¹¹¸·°¨¡œš›ž§³»ÀÀ¿¾»´àÝÖÍÆÀ¹¶µµ³¯­«ª§¥¤¥¢˜††…ˆ‰ˆŽ’˜—Œ|yyxvsz†ˆ–§®±µ¶¸¼½¾ÁÃÄÅÇÊÏÒÐÏÏÎÍÎÎÌËÌÌËÊÇÇÉÉÈÈÇÇÉÉÉÉÈÇÉÊËÊÊÊÉÉÉÉÉÈÅÅÆÅÄÄÄÁÁÁÁÁÁÁÂÂÃÃÂÂÃÃÃÂÁ¿¿¿À¿¿½¼»¼¼¼»»¸¾Ž/ˆ»¸dž_ª³³²±¯°±²³²¯­««®²³µ¡2h¸²·½¾ÄpRÀÈÅÃÅÆÃÁ¾¾º³®©¦§¨ª­±²µ¶·º½¾¾½¼½¼¼¼ºº¹¹¹º»º¹º»º½¿ÀÂÃÄÅÆÉÊÊËËÍÍÊÇý·±¯¬¨¥¢ž›››œŸ£¦ª®°±±²²²±²±®«¦Ÿ §©ª«¥Ÿ‚…Œ”¡ª²·¹¸¸³¬§Ÿœ›œ¡ª·½À¿½¼¹±ÚÙÔËĽ¸¶¶´²°®¬«¦¢ ¤¤ š“‰„ƒ…ˆˆ‡‡‡‚€~‡‹Ž„{yxrlxƒ„Š”¦¬­°´º¼¼¾ÀÂÃÆÉÌÏÒÒÐÏÏÏÎÎÍÍÍÌÌÊÈÇÆÇÈÈÇÆÇÉÉÈÇÇÈÈÈÉÊÊÉÈÉÈÉÈÇÆÆÆÄÄÃÀÀÁÁÁÁÁÂÂÃÄÃÄÄÅÄÄÂÀ¿¿ÀÀ¼»»»»»»¹¹·»Š.ޏµªQa~·±²²°®¯°²³³²°°¯²´´±˜0l¸´º¾Â°EˆÍÄÅÃÅÆÃÀ¾»·²­ª©©ª¬¯²µ·¹»¼½½¾¾¼»»º»»º»¹º¼¼½»»½¼¾ÁÃÄÅÆÇÈÊËÌËÌÌÊÇÿ¸³­©§¦¤ šš›œ¡¥¦ª®¯°²³²±¯°¯­©§Ÿ §¨¨¨¡˜…ƒ‰’œ¤®¶¸·¸¶°«¥ž››ž¦°¸¾ÁÁ½¼·¯ÉÉž»¸¶·´±¯­­ª¦¡¡¢¢¡œ–Œƒ€ƒ…††„€{yxwx~€|vvtlv„‚†”¤««°µº¼½¾¿ÀÂÆËÏÐÒÓÑÏÎÏÎÎÎÍÌÊÉÉÈÈÆÆÇÇÇÈÉÉÉÈÇÈÉÊÉÉÊÈÈÈÇÈÇÆÆÆÆÆÄÅÁÀÀÁÁÁÁÁÃÃÃÃÃÃÅÄÅÄÁ¿¿½¾¾»»»»»»º¹º·¼ˆ-¹¯µ_@¨µ³²²°¯±±²³³³³´µµµ²²+r¸¸¾¾ÇzL¸ÅÃÄÅÅÄÿ¼¸µ²®ª«¬¬¯²µ¹º¼½½¿¿¿¿¿¾¾¼º»½½¼¾¿¿¿½½½¾¿ÂÄÆÇÈÉÊËÌËËËËÉÅÁ¼µ¯©¨¤¢Ÿ›˜™ž¡£¥¨¬¯±±±²°¯­­«©¤ž ¤§§¤Œ‡Œ˜¡«µ¸···´°© š™› ©²º¾¿¿¿¼´ª¼»¹¹¹¼»µµ²°®¬¬ª¦¢¢ žš•†€€ƒ…ƒ€{wtstwvwvtrojq‚ƒ‚¡ªª®´·»¾ÀÀ¿ÂÅÊÎÐÑÑÎÏÎÍÍÍÌÌÌËÉÈÇÆÇÅÆÈÈÉËÊÊÉÇÈÊËÌÊËËÊÉÇÆÅÅÅÅÅÅÅÄÁ¿¾ÀÁÁÁÂÂÁÁÁÁÂÀÁÂÁ¿¾¾½¼½½¼»¹¹»º¹¸¶¹‚-й«­†t³®­®°¯¯°¯±²³³²´¶´³²´Š,~À»¿ÀµItÇÁÅÆÇÆÃÀ¼º¹´°¬ªª«­±´¸¹¼¿¿¿ÁÂÁÀ¿ÁÀ¿½½¿¾¿ÁÀÀ¿¾¾¾ÀÂÅÅÇÉÊËËÌÌËÉÈÇÆÄ¾¹±«©¥¢Ÿœš˜šŸ¡¤¦¦©¬®®¯±±°°®¬¬«£ž¢¦¤ –†€ƒ‰“œ¦°µ¸µ·¶³ª¢œšš¥­·¼¿¿¿½º±¥³±³³µ·º·°°¯­««©¦¡ Ÿœš˜—”Œ€‚}ytrrrrqoqpmifm‚‚‹Ÿ¬©«±·¼¾¿ÀÁÄÅÈËÎÐÏÏÎÌËÌËÊÊËÉÆÅÆÇÆÆÇÈÈÈÊÊÉÈÈÈÉÊËÌËËÊÊÈÆÇÇÆÅÅÅÇÅÂÀ½¾ÁÂÁÂÂÁÀÂÁÁÀÁÁ¿¾¼½½¼½½»º¹¹»¹¸¶³´’.W§«©¬±°®­­°°®¯±²³´²±±±³²²¼‚2–Á»½Å–:£ÈÅÆÅÅÅþ»¹¸´°­ª©ª­±µ¹¼¾ÁÁÂÃÂÃÄÄÄÄÃÃÃÁÀÁÂÁÀ¾¿ÀÁÄÅÇÆÇÈÊÌËËËÊÇÆÄ¿¹²¬¨§¤¡š˜™›œŸ¢£§ª­®®®¯²²°°®««§£››Ÿ¢¡™‚‡™¡¬´¶¶¸¶³­¤žœ›ž¨²¹½ÀÀ¿¼·®¤®®¯°²µ¸·±­­«ª©©¥Ÿœžœ˜““„}€}vtrppnkkmkihch{€ƒŠ›«««¯´¹¼¿ÀÃÅÇÊËÍÏÐÏÌÍÌÊÉÉÈÉÈÅÅÅÄÅÇÈÈÇÈÊËÉÇÇÈÉÉÊËÌËÊÉÇÉÈÈÈÇÅÆÆÅÂÁ¿¿ÁÂÂÁÁÁÁÁÂÃÁÁ¾½½¼½½¾¼»ºº¸º»¹¸¶´²¯T'BWcltz‡”› ¥§©©®¯°¯¬¯®® RI³À½½ÆmLÀÅÇÆÅÄÿ¼º·µ²®«ª­¯²¶»¾ÁÄÄÅÇÅÅÇÇÆÇÇÆÅÃÁÂÅÃÁÁÂÂÄÅÅÆÆÈÇÉÊËÊÉÈÅÅÁ¾º´®¨¥¦¢Ÿš™˜šœ £¤¨¬®¯°¯®°±°°®¬©§¢™œ ¡›ƒ…‡’¦²·¶·¶³®¦¡ž››¢¬´»¾ÀÀ¾º´©¢®®®®¯²µ¸´­«ªª¨¥ ™š››˜“‡€}~~|zxuromkjihggeaev~‚Š›¥§ª­±µº¿ÀÃÅÉÌÎÐÑÑÎÌÎÍËÊÉÉÈÇÅÄÄÄÄÆÇÉÈÆÉÈÆÆÇÈËËËÊËÌËÊÈÊÉÉÉÈÇÇÇÇÃÂÁÁÂÂÂÂÁÀÀÂÂÂÀ¿¼½½½¾¿¿¾ººº·ººº»¹µ²µ¦eD;7630*0348;X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdЧ¶¸¹¹º»½ÀÂÂÆ¯EŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:?:–ÖÌÍÍÍÌÍÊÊÒºl>VŸ¢†IFHLMKMLOT]ds‹‹‘—š›Ÿ¢¥§ª¬¯±´¶¸¹··¸´¯¬¨¥£¡ žœšššš›››œœžž¡£¤¦¦¦¨©©¨§§¦¦¥¥¥¤¢Ÿ›’|{¡ª©ª««¬¬«ª««­­­­¯¯¯°²·¹¸·³­¨¥¡¡¢¦©©©«™Š†„}z{}~€ztqppqrrsvuxxz~€‚‚ˆ•˜›œž  žŸ™‚s_RQaq~Œ”˜›—’“•––˜™ž£¤¤¥¥££¥¥¦¥§«­°²µ¸¹»»»¼¿¿ÁÃÄÅÆÆÇÇÇÈÌÏÒÒÑÐÏÎÏÏÐÐÏц;C>>>¦ÐËËËËÊÊÎÊ•E-:<=9QžÎÈÁÆi'..4>IVfs”˜››’‹‰¹ÄÃÉ—/*'&(&'$"!!6›š™¡a!9q•›œž £¥§«­®²µ·¹»º¹¸µ°«¨¦£¡ŸŸ›››œœœ›žŸ¡£¤¥¥¥¦¥¦©§¥¥¦¥¤£¤ Ÿœ˜ƒ™©¨©ª¬«ª«ªªª©©«««ª¬­­°³³²¯«©¦£ŸŸ¢¥¦¨©©•‹‡ƒ|xxz{}~~ytpopppqrsttvwwx{}}~‚ˆ††‡ˆ‰€vfVO[n‡’–—™–‘ŽŽ””••”–šžžŸŸž ¡¢¢¢£¦¨ª­°²¶¸¸¹º¼¾ÁÂÄÅÅÆÇÆÆÈËÏÑÑÐÑÏÏÏÏÏÎÍÐw:DA?@­ÎÊÊÊÉÌÒ¯c.3<=?:h¸ÑÉÄÀÄc243310.--/1/1552215¨ÇÂɈ140.,*(''&#""!!  9˜™–›Z " EŽª£§©«®¯±²³·¸¸¹¸·µ°­¨¥£¡¡Ÿš™šš›œš›œœŸ¡¤¤¥§¨¨§§¦¥¥£££¤¦¥¥¥¡Ÿš•€’¦¥§©ª«ª¨¨§¨¦¤¦§§¦¦¥¨ª­­¬ª¨©¨£žœœŸ£¦¨©ªš—†€~|ywxyy{}{uqmlmmmnooppopqsrsvvrutqqqrrnnj_UKTl‡””•––’ŽŽ‘’’’”——™™›  ¡£¥¥¦¨©¬­¯³µ·º¼¿ÁÅÅÄÅÆÈÉÊÍÐÑÑÏÐÎÎÏÎÎÌËËk;B@=C´ÌÈÊËÑË„80:=>9B…ÈÑÇÆÃÀÆ^43225689:9877778:<@®ÄÂÊ~.1/+*)'%$#!"! "! @™˜™N!#$%-†¯ª­°±³µ´µ¶¹¹¹¸¶²­©¦¢¡¡žœšš›š››™—˜™œ ¢¥§¨©¨§§§§¦¦¤¢£¤¥¤¤¤ ™˜¡¤¦§©©¦¡ ž™––™ ¡¢£¢£¤¦¨§§§§¨¦¡œ™šž¡£§¨¨‘“‘ˆ€|zzwuuvxy{xsmjiiiiikjllijjjijljhggfeeecb]WMIMe{ˆ˜•““‘‘ŽŽ‘Ž‹’••–™šœž ¢¢¤¢¢¥¨ª­°²´·º¼¿ÁÂÃÅÆÈÉÌÐÐÑÑÏÎÌÍÎÍÌÊËÇ[DC@9I»ÊÅÅγc.4=@<9V£ÐÍÆÇÇÃÁÃU.123242024467:9797=¯ÀÁÉs.1-*%*71/.,)(#" HŸ™”’C(%#"!%$&($.”³°³´µ·¶¸¹º»»¹µ°«§¥£¢¡ž›š››œœœ›˜˜™ ¢¤§¨©©§§¦¤¤¥£¢££¢¢¢¢Ÿ›š—’™¡¤¥¦¥¢œ—‘‰€~€‡˜žŸ¢£¡¡¦©©¨¥ ›˜˜›¢¥¤¤¦_ˆ‡~zyxurstwwxtlifeecccedbcc`b``ac``_^^\ZYXTPF@I_x„˜—”’‘”’Ž‹ŽŒ’’’”•™›ž ¢¡   ¡¤¦ª¬®²µ¶¶¹¾ÀÃÄÄÆÇÊÏÐÑÐÎÍÌÌÌÌÉÈʽL<@@8SÀÃÃÈ”D3::;:;t½ÎÇÆÅÅÄÁÀ½K034.B€yl\TJ:788669Z·½¾Åi10++&v “•“Œ…{tmffdx•“ŽŽ@(x†‚~vY3"$(*-&A«·¶·¸¸¹»»ºº¹¶±­©¨¥¤£¡ žœœœžžžœ›šœŸ¢£¦¨ª«ª¨¦¥¤£¢¡¡¤£¢¡ Ÿœš—”“𡣦¤Ÿ™‡ƒyuuu‡’™›œ ¤££¨«¬«¨£Ÿš–™Ÿ¡¡ £¤4^†……‚}{zwtqpqsutmifa`^^][XY[WVTWWWXYXYWUSQOMID;34]w‹“——”’’‘ŽŒŽŒŠŒŒŒ‘’’•–—™Ÿ¡¡Ÿ ž ¡¡£§ª®±³µ¸»¼¿ÃÅÅÄÇÌÎÏÎÍÌËËÊÈÇÆÈ³@48:6U½Â¾s24<881DËÇÀÀÂÁÁÀÀÀ·B256/_ËÇÉÄÀ·¦—”˜©¸¾½¿Äb)-+++‘²©§§¥¢¡Ÿ¡¡žžœ˜“‹47 ¡¤§«®›Q&*,,.'{¾¹»º¸º¼»»¹·²°«¥¦¤¥£¡¡žžž ŸŸŸœœž ¤¥§©ª«ª¨§¦§¦£¡¡£  ¡ œœš–”•› ¥¦£Ÿ•Šytrtv}†˜œ¡¦©ª¬±²±®ª¦£Ÿ¢¥¤£¢¢¢@5`†ƒ|{vwurooqrtqifc`^\XVURQPMNOOPRRPPOLKID@7,!T}‚ˆ•–”“‘’’‘‰‡‡Š‘“”–˜—šœœžžžž ¤¨«°µ·º¼¾ÂÄÆÆÈÊÍÎÌËÊÈÇÅÃÂÂÄ©93540O¾°V-68760S¤ÆÁ¾½½¾¾¾¿Àó?3451jÈ¿ÀÂÃÃÄÅÌÎÉÆÃÁ¿À¿Â_,.,()’«¤¡Ÿž›š›˜˜˜—•“,= ¡£¥§³«P(..0,O¶»»»»½¼ºº¸µ²¯«¥¤££¡ ¡œžŸ  ŸœœœŸ¤¨¦¦§©ª©§¨¦¦¤¡  ¡ Ÿžœ›™˜–“”›¡¥£š“‡{sqnnpuz‡’›¡¥¨ª®°±¯®ª§¤Ÿ¡¢£¥§§¥¥D@<^ƒ~~{xwutppprrqoida][YVSPPMHFECDIHHGEB@:0!P|„‡Ž“•–•“”––”‘ŽŒŠ‡†ˆŽ‘“–––˜™™™˜™š››œž ¤¨«®´·¹¿ÃÅÅÆÈÌÌËËÆÃÀ½ºº¹¸»™-./.(LŽA&53350d²Ãº¼¼»»¼¼¼¾¾Á¬>4352nÆ¿ÀÀ¿ÀÀÃÄÃÂÀ¿À¿¿½½X++)&*‘¦¡Ÿœ›————˜š™”’†'F¥Ÿ£¥¨©«¸œ6,/12<¤À¼½¼½¾¾¼¹¶³®©¥£¢ ŸŸžžžžŸŸŸŸŸ›œ¢¨¨¨¨©ª¨¨§¦¦¥¢ Ÿ ¡Ÿœš—•–”’”šš‘ƒ{qnnlmmptx{ƒŽ˜ ¡£§ª««ª¥£¢ žœžŸ¢¥¤¤£DCA8f||{zutsqnnnmnnjf`][XVRQMKHD>;99<=:3+' Hy‚†Š“”””•””“‘ŽŽ‹‰ˆ‡ˆ‘’‘’”“’”––——˜™š›œž¤§ª®°¶¼¿ÀÂÆÈÌÌÉÆ¿¸²®®®¯°µŽ%)'()+."-.-,1l·º´¶¸¸¹»¼½½½½À§84493qĽ¿ÁÀ¿¾¿ÁÁÂÀ¿¿¾½»ºQ%'%#'ŠŸ›™šœ›–•—™˜˜•‘Ž€#M§¢§©«¬®²·Q*//34“Á¼¾¾¿¿À¾¹¶°ª¦¤£¢ žžŸ   Ÿž ¥¨§©ªªªª¨¥¥¤¢¢ŸŸ Ÿœš™˜•“‘”—“…}tllmjmmptsux‰“™™œ £¢¢›‘‘—œ›™š› ¢¢¢BCA;=jxwyvtrromllljjhb]ZXUSPMJGB;7542( D|ƒ„ˆ“•–—•’‘ŽŒŠ‹Œ‹ŠŠ‹ŽŽŒŽŽŽ’““““•—˜™˜œž¢¤§ª±¶¸»ÀÃÅÉÊÅ¿µ­¥¡¡¡£¦¯€!&%%%$%))++.y¹¸±²³¶¸¸¸»½»¹ºÀŸ53242tȾ»¼ºº»¾½¼¾¾¼»»º¸ºM%&#"%‡˜–•—–•––“’’”Œ‹Žw\ª¤©­®®²´»_(1263–ýÀÀ¿¼¹·´±¬§¤£¢¡¡ŸžŸ ¢¢¡ Ÿœœž ¤§¨§©«©©¨¦¤¤£¡žž Ÿœ›™˜—•“‘ƒurkijkloqqtstx}†Ž“˜™š˜ƒ||ˆ•˜––˜šž¡¡¢CCC@7?kuvxurqnlkkjjlhc^ZWTRPMIE@<851( =|‚……ˆŽ”•–•”‘Œ‹Š‡‰Ž‹‰‰ŠŠ‰‰ˆ‰Œ’’‘””“’–™œ £¨®±¶¹¾ÁÄý¶ª¡œ˜˜™›Ÿªp!!!!"$%&&%T¶¯­¯°²³µ···¸¶¶¶½‘/100.R˜¤®·º¼ÀÀ¿½¼»¼¹¸··¶H"$ #‰¢›œ™–’‘”•’‘‘ŠŠŒm e¯§ª¯°±¶¹¿Y/0588¤ÅÁÀ¾½·´²°«¦¡¡¡¢¡Ÿ  ¡¡¢£¢¡ žŸ¢¦§¨§©©§¦¤£££¢Ÿœž ž››š˜–”’މ„tolkiiklnqrsusx~ƒ‰ŒŽ’‘‹€tpqŽ““•–šœžžœCBCA=8>outtrqmmlijhiheaZURRQMID>975(  8x}ƒ„†ŠŠ”•“‘ŽŠŠŠŠ‰ŒŒ‹‰‡†…„„ƒ†‰‹‹ŽŽŽŽ’–›¡¦©­±³¶¹´±© ›—•••˜›¤h#  ""$$3›®©­®¯¯¯¯¯°³´´²»„*--,,'(1>Lau…˜Ÿ¡ §¼¸´±E#!FTSYYZ`dagigkpŠ–c " p±«°³¶¶·¼¬A6346F·ÄÁ¾¼º¶´³®¨¢¡ŸŸŸžžŸ¡¡¢¢¡¢ Ÿ  ¤¤¦¦¥¦§§¦¥¢¢£¢¢ š››˜•”“‹€{vqnkihhkmnqrqtv{‚„‡‰Œ…wpor|‹’’•–™š›š–AA@@>=5Bqrrtqnmkifffffc]WSROMHB=74* 0r|‚ƒ…ˆˆ’”ŽŒ‹‰‰……‰‹Š‹Š†ƒ€€€ƒ„…†ˆ‰‡ŠŠ‰ˆ‰ˆŠ–˜œŸ¢¦¨««©¥™–”‘”––—ž`5 !";ž§¤¦§¦¥§¨©©­®¬µt$,+)*+(('&$'/028:;5„Áµ±«? @“’“›_ "&"z´¯´·¸¸»Ãl59573tÄ¿¿½»¹¶µ²­¦¡Ÿ ž›œŸ   ¡¢¡ ¡¢¤¤¥¥¥¥¥¥¤¤¢¢¡  žš˜˜š˜“‘Ž‹|trqmlighmlnpqruwz}€ƒ‡ˆytomrx~ˆ‘”˜š›™—‘@@@?<=;1Jpprqnmkifcacca`[UUPMFB<9+ 3m|…„†ˆ‰Š‹ŠŒ‹Š…‚„„ƒ‚€€}}}||~~€€‚ƒ€ƒ‚€ƒ‰Ž’–˜™ £¤¤¡œ—•““–––”šQeTM£››Ÿ¡¡¢¥¦¦§§§­e))(*+,+,./25446872‰À³­§>Dš˜š¡^##&$µ°µ¶¸½Å~/9875=®Á½½¼»¹·´°©¤ŸžžœœœœœŸŸŸ ¢¢¡¡£¤£¢¢¤¤¤£¢ žžœœœ™˜˜–—”‘ˆzupljifehkkmooqtwz}~€}yslknv|€†‹”˜››š“Š???>=:75,Nnqppmjgedaa_]^[XQNHB@8$ -  )jx€‚„†ˆŒŒ‹ŒŒŒŒŠˆ„~~|xwz||}|{z{{{z|}~{z{z{|~€ƒˆ‘“–™Ÿ Ÿ™—“‘“–˜•–Io˜2j¡–šœ™ ¢¡£¢£­U%%'$)'%'+,.0214785޼°¨£8 GŸž¨\#&%(&‡Àº¿À¿ªj/7;:82ŠÇ½¾½»¹¶³®©¤Ÿœ›››››œ›œŸžž ¡¢£££¡ ¡¢¤¤£¡ Ÿœ›žŸš••••“‘ŽŽŽ…ytpmhfeeeilmnpswz~}}|zvvnikrx~~‚Š•›ž›–Ž…?>>=<:85/)Kpllkihfcb_][ZXUOIE@. - %ct{ƒ…†‰‹‹Š‹‹‡…‚€|{zxuwxzywyxvwxwwxxuttuwx||{}€ƒ‡Œ“—›œ—–’Ž‹Œ”–‘Cq™~ x™’”–š››œžŸ¦F!"#)~ŠveTJB:965767’·©£›0K£ ¤¨V$&&)#^ˆƒzs[:+49=<1xƽ¼½»·³°¬¦¢Ÿœšš™š›š›œžŸžŸ¡ŸŸ¡¢ žž¡¢¡    ŸœŸžœš—‘‘“‘Ž‹ŒŠ‚vqmlifdcehkmnsy}yxvvtpghmrx||€Š•šœ•Š‚?==;;8641-*Gjkgffdcc_]YVRLLE, - - %_ryƒ„ˆˆŠ‹‰Šˆ‰ˆ„ƒ‚}{zyxvwxwvuvvttttttqorsstwwwww{~…‰•—›š—”ŒŒ’–‘:m‹‘g&„Œ’”•–˜˜™›œ7!! 2¦¸¹»º¸²«¦¢œ™––¨¬¡š+*UTTQONKIHIIKJrª¤¤§N$''))&))%(*/34:6:}Á¿¾¼¼¸³¯«¦¢ž›šš™™š™››œžžž  žžŸŸ ¢¢  Ÿžžœ›œ™–”’’‘ŒŒŠ‹‰spmkidcdfiilqz‚…ƒ}wuusrqkchoqv|€†”›ž—’†><;;:54411/):_gfb`ab_ZUMKI:!  - Wrw}‚……‚†‡„‡ˆˆˆ†„€|yzzvwvuutvtssrsrqompqptutwwvwy€‡ŒŽ–›™•“Ž‘Œ6n‡…‰J6Œ’“”—˜—‘.7Ÿ¦«²µºº¼¾½¿¿¾º±¥š“‚%C”‘‘”••—œ£¨§¥¤ªL$((*,-020234213PŸÊÀ¿¾½º³°¬¨¤ œš˜™˜™™™™šš›š›ššššœ›œž ¢¡¡ Ÿž››››š™–•“‘ŽŠ‹‹Š‰‡~vtpnhcbegklqw„ƒxvtqonlhdhnow‡’ ž™ˆƒ{><::85434311,1Oa^\\ZVPNL7 - - Lqx|‚ƒ‚‚‚„…ˆŠˆ‡‡‚{{ywwvtuutttrqsrrpooqprrqssttx|†‡–—–“‘‡‡†ˆ‰‰6o~|}‚4G‹ˆŠŽ‘’’”‡%:˜š¢§¬°²³³³´²°¬¥šŒw J‹ˆ‰Œ‘’–—™œ¡§¨ª©¬M&++-/-+.,-1/?b‘ÁÌÂÁ¿»¸±®¬¨¤¡žš˜—˜—–“’“””’‘••”’’–—–™¢¡Ÿš˜—˜˜–“““’‘‹ˆˆˆ‰ˆˆ„}|ysnidbdhmu{ƒˆ„€yuuspolhdefimw…“›¡¡Ÿ™”ƒw<;9877655331/.)?ORRQLIA"   - LVas°ÂÉÅÁÁ¿¹·´¯¬©¥ œš•“‘‘ŒŠˆˆŠ‰ŒŽŽ‘Ž‘•–˜˜••–“’‘”“Ž‘’Œˆ‡‡††‡ƒmtxslfcaensy†ˆ†{tpqonkecfghio‚“¡Ÿ›•ˆ‚|x;::877442320.--3?A@EG5 - *p{~ƒ„„„……†††††ˆˆ…~€€}{wvtqrrqrnqqqpnmnoppponpqruxz~†Ž’“’‹ˆ„‚„„/"uƒ‚~{€fp‡ƒ……ƒ‡t>‹ˆ‰‰‹ŽŽ‘’Šˆ„€€…a]‡†‹ŒŽ•–™Ÿ¥«­¯±³¶Q-16;<ŸÁÄÉÌÍÏËÅÃÿ¼¶²¯«¨¢™–’Ž‹Šˆ‡ˆ†…„ƒ„„ƒ‚‚…†„ƒƒ„†ŠŒ‹Š‹Œ‹‹‹‹‰†„„ƒƒ„‚aanmjeabhmu|‚„…‚{tqoomlhb`cgiiju„”š—”Žˆ„‚zw97788842310/,0;A@;8=4   - $c{€‚„„„…‡††‰‰†…‡…€€~}{xwurqsspnoooonnmnoooppqruuwz}‡Ž’’’Œ„})!wƒƒ€{Y(ƒ~€€~‚jDƒ~€ƒ‚ƒ‚‚€~}~‚Ueƒ†ŒŒ“–—ž¢¨­±³µ´µQ2369H½ÍÉÊÉÇÇÇÆÄÿº·²®ª¦¡œ—‘Š…„€€€}}|}}}|zzzz{z|}|ƒ‚ƒ…†ˆŠ‰ŒŠ‡…†Š‰‡ƒ‚‚€~€‚€bYdhecabgkxƒ„|upnnmmjd__cfhhjmr|‡‹‹ˆ‚€}ww5676665332/,1BGDA:90  - _}‚„……„‡ˆˆ‹ŒŒ‹‡†„‚€~}|{zwutttpopnoopqpprrsuvxwxy{{}‚‰’’‘‡v%&w€~~{y‚G=…~||z~_H…|}€}{||yxxz||}„Ks‹‹‘”˜›ž¥§«°´¸¹¸´J1344J¹ÆÇÈÉÈÈÈÆÃ¿»¶¯ª§¢œ˜”ˆ…ƒ~|zzzxwvxywvwvuuvtvyz{y{~~„…ˆ‰†ƒ‚ƒ‚~}}~~}`[`fccbcglw€ƒ‚|wsoonlke_^`ceggjkmsz‚…ƒzxz}578645542219EIGB>?:   - - Ly‚ƒƒ„„†…†‡‹‹‡……ƒ€~|{xvuusurqqqpqrqrtuy{~ƒƒƒ‚‚ƒ€„‹ŽŽŽŽ‰‚}{m )z}{{|}}~~4Mƒzzx}S.RV[bdhopsvv{~|€~„E"ƒ›—–›ž¢¤¨«­±´¹¸¹¹»´I5693M¿ÆÈÊÉÈÇÆÃ¿º´¯§ š–Šˆ…€}|zxwvwvttutvtrrsrsqppqsuvxyyz|~€‚‚‚~€~~}zzyz}{`[`baabehox€~|xsnmlkhb^]beggeffimx€€}yw|ƒ7865533414BJIGE@<@  - :x‚~}€€€ƒ„„……ƒƒ€}{zwxwuvuurrrqrru{€…‰Ž”“’“Ž‘‹‰‹‹„}yui"({zzy{|||v&^ƒxv{M#(+06:R~„=`twz†‰–˜š›žŸ±»¼À·H7776XÆÊËÉÈÆÃÀ½¸²­¦ —’Œ…‚|{yvuttrrrqsttrqqpqooooooqrsuvuvxyx||||||~~|{zvvvvz|x`[___`bcis|€ztnmjigc^\`dfeggbglp{}}{vyˆ77534322>JPJIFD>2   - - .wƒ~zyz|}}€ƒ…„ƒ„†„ƒ}}}||yxwwvrrtvz†‘—𠢤¤¥¥¢žš—”Œˆ†ˆŠ…|wro_PJE>Gwxzy{yzxx|dl{rwF/€€5!$&)+.1147›ÂÀõF7887hÌÈÈÇÅý¹´±¬¥ž—”Žˆ€|ywwwtrsrsrqprrqponllmmopnopprsrrstuuwyxxxyyzxwvtuvuwzv]Z[]_`delu}‚‚|tojkhie]Z\bdgffhfms{~|{{vv|‡7553229JVTPJHE:/!  - *f€|xxwz||~}~‚‚„†„…††…ƒ‚ƒ€{zyzyvy|~†Ž– §ª¬¯³±²µ´±¬©¢™’‰„ƒ…†…€ytoqsx{{wvwx{{|}|{z€V#&pvyB5€0"""%'(*+,//15ŸÆÁÇ®B:;<7rÍÈÆÃ¿½·²¬¦Ÿ™”Œ†€}zwvutssrrqrrqqpomlkjiklmmnmmooppppprrsttvusstuvuuututuwoZYZ^aceinv}€|vqniigfa[[aeejhhimu|€|}|zvz†”œ52123ASWTSKE<70,! - - - "[y~zwtrswwyz|€€„††‡†ˆˆ……ƒ€{{{yz‚‰‘™¤­±³¸¹»»»º¹¶´±¨œ„€‚„ƒ‚|wtrqprtsstuyz|}~}}|sjbXOI;[ww@A‚~ƒƒ. ""#&')*-/2359©ÇÂÇŸ76530…ÌÅľ»¶±¬¥ž™Ž†‚|yvutttrrrqqqppoomkjihhhghijijlmmmnopoqrqppqssrsrttstrsssukVW]`efimsy€yrnlgghe\Y\aefjkjnt|„…~{zvx‚‘œ 31/-3AA?A=75320/#  - - - - U{{wrrqrrstyz}€ƒ„…ˆ‰ˆŠŠ‰‡‰‡…‚~~‚…‹“𥭳º»¼¼½¾½º¸µ±ª£˜Š~{|~€€}zyyvuttuuuvz|||~}|}}}|x{}|{xxoaZSKD<62,)$#I‚€†) "!#%')*+-33/<­ÃÂÆ›S\bv‹µÇþº³®§¤ž•†~zvuttsrqppqppoommljhhghhfdghfggijllmmnnmnpqppqqrrpqrpqqrpqtqbadfhjntyƒ}wojigge^XY]cfjjlpsyƒ‰†}yut{‡“œ 1.,./.,.43334422' - - - - - Lyxwtqpqrssty|„‡‡‡ŠŠ‰††††‡‰†„‚ˆ‹’›£«²·¹»»»¼½¼¹¶²«¢—Œ~{zz}ƒ‚‚~{zzzzy{zz|{{€‚|zzz{}|z€{zyzywxxwspnlhcYTQl…‰ƒ?2.2300.1656;@FGNSZv¾ÂÅÆÃÂÊÏÒÐÉþ¹´®ª —ކ‚|xvsrrqopooopoollklljjijmlifiigeffiklllmmnlnpomnopomoqponnnprqighjiovƒƒxqlihge`YX]cfijjmsw~…†€zvqs‹”›¡.,+-.-/043566753   - - - - - - - - Euttsrsssstux{‚†‡‡ˆˆ†‡ˆˆ…†ˆ‡………‹Ž— ©±¶¹»¼¼¾¾¼¹´°­¨š‚zxyz}…………‚€~€~ƒ‚„„„‡…‡†‚‚‚€ƒƒ„„€|{xyz||~}~€‚‚ƒˆŽŽ‹ŠŒŽ‘““•™ž¡¤§¦©¬±¹¼¾ÁÆÍÊÈÉÉËËÈÉÇž·²ª£›‘‰„~yvtsrponmmmnonoomklnnnoorvurpmjihhfegjjilloonoononnmmlopnmlmmqpkijkoxˆ†{tnieeb`XV[_dghkmqw€‡ˆ€zwurx‚Œ”™ ,+,./00224677742  - - - - - - - :wwvvuvttuvwxy~ƒ„ˆŠ†…‡‡ˆ‡‡‰Šˆ‰Š‹•œ¢¬²µ·º¹º»º¹³­¤˜‘yutux{}ƒƒ†‡……‡…ƒ„„„„„ˆ‹ŠŠ‹Š‹Œ‰‹‹‰‰‰‹‹ˆƒ~ƒ‡†‡‰ŠŒŒŠ‘“‘“—˜œŸ£¤§¬²¹º¹½ÁÁÃÆÅÇÊËÉÊÊÊÉÈÉÇž·°«¥šˆ‚~{xtqrrponnlmnnnmoomnopprsux{yxvrqonkigfgigiklnnonnolllkkklllkmnpoljkqz‚„‡…zslhfccaZTW^dfhikqv{„‰…zvssu}‰‘˜œ +++-/00344455762   - - - - - - - 1s{}}}|zxvvwwvzƒ†‰‹ŠŠˆˆˆ‡ˆˆ‰‹‹‘•™ ¨¬±³¶¸¸¸¸µ°¨ž˜ˆ~usqqqty|€‚†ˆ‰‹ŒŒ‹ŒŒŒŽ’““”“‘‘’“•“’“’Љˆ‡‹ŽŽ’”•““•˜——™˜˜¡¤¦¨©­°°·»¼¾ÁÅÅÇÇÈÊËËÊÊÉÈÇÈÇžº´ª¢›’‰~zwutrqpqpnnnnonooopppqqsstwxz|{{zywvutppjhhhjklmmmlmnljkkjjjjkijnrplov{„‚|rjgedb_]USX^cegimsx€‡Šƒxurrx‚Œ“šž )**,-./11214112*  - - - - - - - - +n€„‡‡…„€|zzzzy~…‰‹ŠŠŒŒ‰Š‹‹ŠŒŽ’—𣫭®°²µ··´«¡œ–‹ƒ}vurqqsuw|€ƒ‡‘””••••–˜š›œœž¡Ÿ››žš™™˜•’Ž‘”““•—–™šœŸŸœœŸ§«­¯±´¹¼½¿¿¿ÁÃÆÇÇÈÊËÊÊÊÊÈÈÈȼ¸´¬¡™‹‚~yywuspnonnnmnnnoprrrrsttuuxyz{}}}}}}{|{zxunkjkkmnkllmmlljlkjihhhikmqnov…‡…~yrleddb`\VTV\bdfilot|„Š…}wsot|‡•—œ&''(())(''''%$#   - - - - - - - - "k‹‘Œ†ƒ|~}}„‡Š‰‡ŒŽŒŽ‘•™¤ª¬­®¯±´²­£šˆ€|yustsqsx}…‹’—™¡£¤¤¢¡žžŸ¡¤¥¦¨©ª©¨§§§¦¥£¡¡Ÿš–•—ššœ››œœŸ ¡¢¥¨¦¥¥§­°³µ¸¹¼¿ÀÃÄÄÅÆÇÈÉÊÊÊËÊÉÈÈÇÇÇý¸°¦•Œƒ}zxvvspnmmmmlmnpqqstrrstvwwyz{||}}€€}||}}|{xqmlkmmklkllmlkjjjiighfghiojju€ƒ{qjd^``^_ZRVZadefkkox~„…}xvqqy€‹‘“–™œ!! # ! !!"!   - - - - - - - - ]˜›š˜•‘Œ‡„„‚~}‚…ˆ‰ˆ‹Ž‘’‘’”–™ ¦ªª¬¬­°±¬¢™…|{xusutsyˆ‘œ¦¬±¯°®°´µ·³¯«©ª¬¬­®±²¯¯®­¬¬ª¨¦¡œŸ¤¦¦¤¡ŸŸ¢¦¦§§©¬®°²³±´·¸»¾¾ÁÄÅÆÇÉÊËÌÌÍÌÌÌËÉÉÈÉÇÆÀ¹´®¦š’ˆ‚{yxvtrpnllkjlnppqrsuvuwvwxz{{{|}~}~~}~}}}||ytqnnljkljljkjjjjhigfggfgjjYU_ijhfg`a^\Z]]ZTRX\`ccdgmpw}€|xtrorz…Ž“•˜™"""" !!##"#"!    - - - - - - - - Nˆ—Ÿ¡Ÿžš—‘Љ†ƒ†ˆŠ‹ŒŽ‘’’“”–™œ¢§ªª©ª«­­§›’‰‚€~xwvyz~‡˜¥«£‡mWMJJP_œµÂ½³³³³³´µ³´µ´´±¯­«¥¤¦«®¯«¨¦¦§«­®°°²²µ¹¼¾¾¼½½¾ÁÂÅÅÇÈÊËÌÍÍÎÎÌÌÌÊÊÉÈÇþµ¯¦ š‘‹|xvutrqonlloqqssrqstvwz{{{|}~~}~~€~~}~}|||xurppnlllkkjjjihihffdeeeicMJKSSTWYXY[[[[ZUQTX]bdeehlqx~}xurnpv€‰–˜˜š› !!"""!"   - - - - - <€‘Ÿ¦¥¥£ž™”І„ƒ…Š‹ŽŽŒ’““–™š¢¥§¦¦§¨ª©¥™‹‡†‡†ƒ…ˆŠ‹‘š¨±–c<0034453048Gw¯Ä»¹ºººº¸¸¸¹º·µ³±°²¸º»¸³±¯¯²´³´·¸¸¹¼¼¼½¾ÀÁÀÁÂÄÆÆÇÉËÌÌÍÍÌÍÍÌËÊÇÇÅÁ¼³­§ ™Š{xursqpponokWZ]aitvuxxxz|~~}~~~€€‚€~~}|||zyxusronnlklljifihfbcbbcf[FIJJMPQRVY[ZZZVQRV[^_cfeimqy}ytrpou~…Š“™™—š› !!" - - - - - - - - - - )q”Ÿ¥©©©¥¡›–’‹Š†ƒˆ‹‘Ž’••—››ž¡¡¢¡ £¦§¨¥¡œšœ›š›Ÿ£¤§«·§f0(/7;889:9<::5.@{¼Ã»¼¼¼¼»¼½¼º·¶¶¸¼ÃÆÊÆÅþ¸¹ºº»½¼¼¾¿ÀÀÀÂÄÄÄÄÆÅÆÉÌËÌÍÌÌËÉÊÉÉÈÆÄÀ¼¹°©¢”‡ƒzwutqrqpppoy>j{xxz|{|~~~~‚ƒ„…„…„ƒ‚~}}}||||zywvtrollkkjhghgeaababgM=BFIKNOQVYXYXWQOQVZ^`bdgkotyyuopqot…Š’–•˜š - - - - - - - - - - - - `Š—¢©«ªª©¤žš–‘Ž‹ˆŠ’“’“‘“–—™šœŸžŸŸžŸ¢§¨ª®²·¶²²²´·¹¼ÄšB'1524:95457;96784.Q©ÉÁÂÀÁÂÁ¾»ºº»¾ÄÇÈÑ—q‚Ž™¶½»»¼¾¾¿ÁÂÃÃÃÄÆÆÆËÐÐÏÎÉÊÉËËÊÈÉÊÇÅÄÁ¾¹´­£§c&*),+Mxstrqppont[f|ywxz{|}~€€„†‰‹‹‹‹‰ˆ…„‚}{{||}~~{zzxusrqnllkjhgfccaaac=3œÉÂÃÃÃÃÂÄÇËÌÌÌÆV16697P»¿¾¼¾ÀÂÃÂÃÄÇÇÇÇÏo0240KÈËÉÉÈÆÄÃÁ¾¹¶°¨ š“‰//qrqqqqpoyHi€|||}~€‚‡Œ”••”“‹ˆ†„~€€€}~}zxxwvurrrpopmlhdd>.379>DFJLNQSSPLOSUZ^^]_dghnsspnoprv|‚‡ˆ‹ŒŽ‘   - - - - - - - - - - - - -  `‘Ÿ¦¬°¯¯®«¨¥¤¡›˜™˜˜›žžœ››™—•’‘‘‘“˜¢°ºÁÅÆÈËÍÍÏz-58::0N•ÁËÊÈÈÉÊÌǯf59;;:.PÀÆÃÂÁÂÅÉÊÌËÄÇ{+633576œÅ¾¿¿ÂÃÄÃÆÅÇÇÉÊË\49:4eÍÇÇÇÅ¿¼¹¹¶­§ ™‹t!-oqqrrsqsck€€‚„†‹‘”˜™˜—•“‘Œ‰……„‚ƒ‚€€€€~|{{zyxuxxvwuturp]/+18;;ADGJMPRQMKQTZ]]^]`dginropootv{|€‚ƒ…„†ŠŒ   - - - - - - - - - - - - - - - R€Žœ¤¬²µµ´±­««©£Ÿœœœœœž   žžœ›•’ŽŽ•¨´»ÀÃÇÊÉÑŠ-4696/sÁÑÉÅÅÅÅÅÆÄÁÃÃ…79<1MœÃÄÃÁÂÅÊÌÎËÂÆ¢//10114.kÅ»½ÀÂÄÄÅÇÇÇÈÉÍÁH6885~ÌÄÅÄÀ½º·²­§Ÿ™”‡ƒ`,npqqrsrw1n‚‚ƒ„†ˆ‰Œ“–™™™˜—•’ŽŒ‰‡ˆˆ†††††‚‚‚‚€|~|}|{{{|}|{zyws3%+-3:;?BEHMQRPJJPUY]^_`acdinmlmoswz„…„‡†‰Š‹Œ   - - - - - - - - - =yˆ˜¡ª±´¶·¶³®­¬«¦¢¡¢  ¡¢¡ Ÿž™“ŽŒŒ‰‹Œ”žª³¸¼¾Àͱ707<71ƒÎÎÇÈÈÈÆÆÅÆÆÅ¾ȉ32a¸ËÃÄÅÆÉËÌÌÉÀ¼¹H&0/0001/?³½¾ÁÂÄÆÆÇÇÇÇÇË«:9874‘ËÁÁ¾º¸³¯ª£œ˜‘Š‚B#kpprspyRtˆ†ˆˆ‰ŒŽ‘•˜™œ›™—•‘ŽŒ‰Š‹ŠŠ‹Š‰‡…„‚~~~~€€}||U#*,7;?ADFKPOKIJOUY\]_``ceiljklov~ƒˆŒŽ‹ŒŽŒŽ  - - - - - - - - - - - - 'j‚Ž™¡¨­¯²´³°¯®®­ª¥£¥¥¥¥¤¢¡ žœ—“‹ˆ‡…†‡ŠŒ’©¯²´¶Äs+:<=2ƒÒÇÅÆÅÅÈÈÇÇÇÆÅÅÃÃÉ„ÆÌÅÆÇÇËÍÌÊÆ¾´¼n!--../1241†ÆÀÁÂÄÅÆÅÆÇÇÇÏ”4:889ÆÀ¾½¹´®©¢”Šƒ|zy.eqrrrshv‰‹‘‘’”˜›ŸŸžœš–’ŽŽ‘ŽŒŒ‹ˆ…„‚€€‚„‚€~|r# - !(,7AGJJJIHJMPUXYZ[\_abeknt~‡Œ’”“”—™˜———˜™    - - - - - -  - - - - - - - - - - - 0u›¢¦ª­°°²±¯­­©§¥¦§§¦¥¥¥£¢¡›“І„ƒƒ„ƒ„†Š“Ÿ¨°¹¡-+052ÌÄÆÈÈÈÉÈÇÆÆÅÄÅÅÅÅÅÇÆÆÇÇÊËËÌËÅÁº¸³E(0./+::279:9¦ÅÀÁÁÁ¿ÁÄÆÅÂÆa3979E¯¼¸²­§£œ’‹„ƒ~zxu|ADtsrxZ)…‘‘•––––˜™š››¢£¢ žœš™™š™™š™—•’‘ŽŠˆ‡„…ƒƒ„„†ˆ‡Šˆ‰ƒ|j "(+/9?CHIHFGHLNRUXY[\]^_ajr~…‹Ž‘”•———•”—›œ  - - - - - - - - - - - - - - - - e€Œ™¢¨«¬¯°³²°®­¬ª¨§§¦¥¤¤¥¥¤£¡˜ŽŠˆ……‚‚‚ƒ…Š“ž¨´‘%(),2¦ÄÁÄÆÆÇÇÈÇÆÅÄÃÃÄÅÆÆÈÇÇÉÉËËËÉÆÂ½¸¾s$/0/01˜x-;8;6oÇÀÂÁÂÂÂÅÆÄÃÃS4765F°¶±ª¤¡™•‚€|yxutn"$%prwo"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4BBCDEFFGGIMRRSVUVWX\`_cfhlnoonnsyxy{ - - - - - -  - - - - - - - - - - - - - - - - - - -  fŠ˜ §«­®°±²°²´³²¯°°°°¯°¯¯­¬©§¢œ–‘Œ†~~~~‰\­¨­°¶º¾ÁÂÂÃÄÅÆÆÆÅÃÄÅÄÄÃÅÃÂÈ£00113+I½À½½½Ãm08552lÇÀÃÄÃÂÀ¾Å‡+,(%!A—Іƒ~|wwxvuutsv1f|}/yš–M\ž›žŸ ¢¤¦¦¨§¥¤£¢¢£¤£¢££¡¡   žœœœž¢¥¨®±³·¸¹ºº¹¶²¬_ - -  (07>AAEGDGHFGJMPPQSTUVXY[_acedgihkmquw~„ - - - - -   - - - - - - - - - - - - - - - - - - - Nz„“ž¥¨¬¬¬¯±²²³µ´±²²±±°²²²¯®«¨¤ž˜“‡ƒ€€~~~!F¥¡§­²¶¹½ÁÃÃÄÅÄÄÃÅÂÂÄÃÃÄÅÄÈ¿N,300/7¤Å»»¼¹¾œ13223C¶ÄÃÅÃÁ¿¾Ãx%&" Eƒ|yxvuuvutttr)p‡hO£š™Hd¦ž¡¢£¤¥¦¦¦§¦¤¤¢¡¢¢¡¡¢¢  ŸžžžŸ¡¦ª¬°´¸º½½½¼»ºµ´‰#  - - &/8?BBFJFEJHGILOOOPQTVXXYZ\^^acdhjnr}ˆ“œ  -   - - - - - - -  - - - - - - It€Ž›¡¦©ª©¬¯±³µ¶´²±±°°±±°±²²¯­§¡œ•Іƒ~}~†Z]¥¡§¬¬¯´»¾¿À¿¾ÂŪÂÅÂÂÂÄÄÃÊl-630.,€Î¾ºº¹¸¹½M-3251ŽÊÂÅÃÀ¾¹½l!% NŽ~|xutuuuuttui"%z‡‰A&‘£ £F ! k§¢£¤¤¥¦¨¨§©¨¦¥¤¢£¢¡¡  ŸŸŸŸž ¥ª®²µº½¿ÁÂÁÀ¿¼¸µ§J  - - - - $-8?EGIKJFGIIIHLMLLPRSVXWWY]^`bfltŠ™¢£   - -    - - - - - - - - - - - - Tk|‰”£§ªª¬¯±´·¹¸¶µ³²²²°¯°°°¯®¬§¢Ÿ—‹†ƒ~}}€€„6b¤¨«¯´¸¸»»ÂÂ~?vËÁÁÂÃÀÌ19632/.[…Ÿ²¼¿ÁÂÌ€,5460^È¿»¶ºa !V…}{xvuuuuvvuwc*„’œ}"d®££¨M!!"lª£¤¤¥¦¨ªªª«¬¨¦¦¤¤¢¡¢¡Ÿ  Ÿ ¡£¨­²µ¹¾ÀÂÂÃÂÂÁ¿»³®k - - - - - -,7HGFHJKIGIIIGIIIJLOOTUUVY^`cnt„—›œš™•“  -  -    - - - - - - - - - - - - - - agm€Œ—¢¨ª«®±´µµ¹¹¶¶´³³³²³³±±±°®¬§£ž•ŽŠ…‚}~€y$G•¦§­®®®³ºÁ®_/83ɾÁÁű?56651--&$,>Qit{…m364459¬ÄÀ¾º´®²S_}{yyzyvwvvxzy|[/›žŸ§V  1¢®ª©«U!$o®¤¥¥¦¦¨ª«ª¬«ª¨¦¦¤¡¡¢¢ ŸŸ ¡¤ª±µ¹½ÀÂÃÅÄÃÃÂÁ¾º°‹* - - - -/HKFHJJKKKIKIGHJKNPSSVZ_cglsŠ‘‰‰Š‰  - -    - - - - - - - - - - - - $hfg{‹”Ÿ§«¬®±µ¶···¶³²³²²²²³´´´³±°¬¨¤–‹†ƒƒk.g‘§­®°²l9'5997’ÈÁÄÄb287670+*+)*-*+-.0076446,tº¸¶°§§Bk{wwyyyyzzz{}†W0˜¤¥¥«ž2#$s¶­®«¯] #$k®¨©¨§§¨ª¬¬ª©©¨¦¥¢Ÿ ¡¡  ¡¢¦«±¶»¿ÀÂÄÅÆÄÃÂÁÁ»²£F - - - - - - 3HFIJJKNQQNPRPRTWX^`adjnruz|~|~‚ƒ…     - - - - - - - - - - - - - - 'kda{•ž¦ª®°²³µ·¸¸·¶µ³³´´³³µ´³³³¯¬ª¦¢ž˜‘‹ˆ…€€}{ƒc'E\a[C+ )12482O»½Ä‡3989:3.%#&'+.232367754351D·ºµ²§ š7$xyxxzzz|~~€‚†Š“` 1¨«­«¸v'$=¬²±°¬¯`"%$b±ªªªª©©««¬«ª©§¥£¡Ÿ  ¢¢¤¦©­±µº¿ÂÂÃÄÅÅÃÃÁÀ½¶¯m  - - - -6@DCCGMQWWWYYXX\_adceikmoqrtttvx|}€ - -    - - - - - - - - - - - - - - - ,nf_o‡’›¦®±³´³³¶¸¹¸·¶µµµµµ´µ³³³²¯­«©¦¤Ÿš“Ž‹†‚€{z€`!&(,03.<¿À­=79:=6Kš‹`='$(./1466242256*ˆ½±ª¡™’,3yvxy{~ƒ†‡ŠŽ™k"#%3«­°²´³J%$u¹²²±®³l$(%\±¬««ªª©ªª©«ª§¥¤ Ÿ  ££¢¦«¯²·»¿ÂÃÅÆÅÄÃÃÂÁ¿¹´’( - - - -  -4;;:>EGMSTWTUTVXZ]`cegfijklqswwy|  - - -  - - - - -  - - - - - 4rj]cy‹—£ª¯±²³´´·¹¸¸···¶µ´³³´³³´³±¯¬©§¢ž˜•‘‡…‚}||‚s0 #%)+&J¤ÄºÁ_.;;<:7ż»®”vVGC?<>BD91541)I¯¦Ÿ˜‘"E}wz{|€„…ˆŠ‘–œo#%%(/–¯¯°²²¹•-<«²²³±¯µz(*)#P±®®­««©©©¨§¨¦¤¢žŸ¡¢¤¦§«®³·½ÀÂÅÆÇÅÄÄÄÄÃÀ¼·¦L  - - - - - - - - -#5435;=AEJNPQSQSVWZ^_cdddinsuw{†‡  - - - - - - - - - - - - - - - - - 9qj^`mƒ“ ¦«®¯±³µ¸¹º¹¸¸¸¸¸µ´³´³µ¶´´±¯­«§¢žš—”Žˆ…~}‚}L"#&f²¿·À-68<<5}ô³³´¹¾»º·´²³¸À–151-,$‡¤—Œy%5/-/...--..(o ¦{!''((±°°³´³»n{º³´³°¯²*(("A¯±±±®­«ªª¨¨©§¥  ¢¢¤¤§¬®²·¼ÀÄÅÆÇÆÅÃÄÄÄÂÀ»²u - - - - - - - - -%0147:;=BHLOSSQRUW[\_cfhlmtx|€„‡  - -  - - - - - - - - - - - - Csk^]f|‘𣍫®°³¶¸¸ºº···¹¹¸¶µµµµ¶·µ³²°®­©£Ÿ™”ŽŠ‡‚~ƒuF<е²±¸«>388;4ZÁ¹²²²²³²¶¾ÃÇÉÊÇÆÂN*-*(LŸŠˆp hª¨ˆ*++,*†µ²²´µ´µµµµ¶µ²±°³˜0))$:©´³²±¯¬¬ª©©©¦¢¡£¤¤¥¦©¯´·¼¿ÃÅÆÆÆÆÆÄÄÄÂÀ¼¹@  - - - - - - - - - - - - ,2269:<@DIMPSTRVW[^bfimqvyƒ…ŠŽ’•  - -   - - - - - - - - - -  - - - - - - - Gvm_]aoˆ•ž£§¬®±´··¸¸¶µµ¶·¶µ³´¶´µµ´´´³±¯­ª¦£ž›—“މ†…‚‚…‡‰xP/ ,Gz¡«ª«®´x4;864<¨À³±®¬­­°¶¿ÅÅÆÅÃÀÄ‚#)($ %‡……b #j¬¬‘1.-.+~º³µ¶¶¶µ¸º¸¸µ²±°´¢6)*)5Ÿ´²³²±¯­¬¬«¨¤£¤¤¦¦¦©®µº¾ÀÂÅÇÆÆÆÆÅÄÄÄÁ¾¶©w* - - - - - - - - +/48;>BBFLPRUWWY[`dhmty~‡ŒŒ‘  - - -   - - - - - - - - - Rwp`\af{™ ¤ª­®³¶··¶·¸µµ¶¶µ´´´´³²³³³²°®­¬©§¥¡ž›—”‘Ї‡ˆ‡ŒŠ‚ztt}£££§ª®²§¤¦ š–ž¾¸µ¬©©©«³¾ÅÈÇÆÄ¿¼¹§/$$ X…‡T $d­®™5,-,-s¸³µ¶¶··¸¹¹·´²±±³¨<(++.“µ²²°°°¯¯®¬§¥¤¤¥¦§©¬´º¾ÂÅÇÈÉÉÇÅÆÅÅÄþº¶‰c' - - - - - - - - - - - - 6:ƒ‚{l_cccedgz‰”Ÿ¨¯²´¶¸º»¼½½¼¼¼¼¼»¹¹·µ¶µ³±±±²²³²²²°¯®®¬­­­«©§¦£¡Ÿš—‘Їˆ†ˆˆ‰Œ•¢§¬³·»¾¿ÂÆÆÄ½»¸±°­ª­ª¨©ª­¯°²¶¸¸º»º¹º»¼¹¸··»½¼¼¼»»º·¶µµµ¶³´´µ´²±¯®®®­­­­°±°°²²´³³µ¸»ÁÃÆÉËÌÎÌËÌÊËËËËÊÈÆÃÀ»·³°­©£¢˜nadgioo` - - - - - - -     $&&$&(&)’’‘‘’ŒG  - - - - - - - - - - - - - - - - A‰†~o`bcgihgo‚™¤ª²´µ·¹¹»½¾½½½¾½¼¼»¹···µ³²²±°¯¯¯±°°°±±±²±°¬ª©¦£¢ŸŸ˜•’Ž‹‡†…‡‡ŠŽ“˜¦¯¶¹½ÀÄÄÆÈÈÅÄÁÀ¼º¹¹·¹ºººº»¼¾ÀÃÁÀÀ¾¿¿¾¼ºº»½¾½¼½¼¹¹¶µ´´µµ³´´´´²±°°°±°±±±²³³µ¶¸¸¸¹¼¾ÃÇÉËÌÌÌÌËÊËËËÊÊÈÇÄÁ»¸µ²®¬ª¨¡ž€cbehlrmb" - - - - - -  -  -  -  #&$$'&&(‘Ž‘ŽŽŽŒ‰J   - - - - - - - - - - - - - - - - KŒ†ƒ~saachkkkkv…’𤭱´µ·¸»¾¾½¼¾¾¾½¼¹····¶µ³²²±±¯­®°²²±²³²²³±¯¬ª©¨¦¥¡ž™”Œ‰ˆˆ‰‰Š“œ£«±¸»ÀÇÄÄÅÆÆÆÇÅÃÂÃÄÅÆÅÄÄÅÆÅÆÆÅÄÃÃÃÃÁ¿¾½¾¾¾¼¼¾¼»º¸··¶¶µµ¶´µ¶³°°±²²²µµ¶¶·¸¹º½ÀÂÃÅÇÊËËÌÌËÊÊÊÊËÊÉÉÆÅÿ¼¸´±­ª©¨¤™Šjcfikmund# - - - - - -  - - - - - - - -  "#%$%'(''ŽŒŒ‹ŠŠ‹J   - - - - - - - - - - - - - T‹‡†‚va^chjlmklx‡•Ÿ«°°²µ·¹½¾¼½¾¾½½»¸¶¶¶¶¶´³³³³³²±²²²²±°±±²´³³²°¯®­¬ª¦¢™•Љ‹Œ‘–ž£­¤OHuÆÁÂÃÄÅÅÆÎÑÐÑÊÊÉÈÈËÒÑÏÈÅÉÎÌÍÎËÉÊËÆÀ¿¾¾¿¾¾½»¹¹¸·¶¶·¶·µ³±±³³´¶¹º¹º¼½¿ÃÅÈÊËÍÍÍÌÊËËÊÉÊÊÊÉÉÇÄ¿º¶³¯¬ª¨§¥¢œŽthfiikntmf$ - - - - - - - - - !!"$$$'&&(ŽŽ‹Š‹Šˆ‡‡‰D  - - - - - - - - - - - - ^Љ„|b`cfklllgo“§®±´¶¸¹»¾¿¾¼½¼¼»¹¸·µµ¶µ¶´´µ´´µ³´µ³´´´µµ´µµµµ¶´±°¯¯¬ª¤ œ˜—–‘ŽŽ”šŸ¯e ,,žÅ¾ÂÄÄÄÄ~hl˜ÑÊÉÇɽr’ÊÉ­jhfdbgrŒ«ÇÉÂÁÁÀ¿¿½»¼»º¹¹¹¸·¶µ´´¶·¸¹¼¿ÁÂÄÅÇÉÊÌÍÍÌËËÊÊÊËÊÉÉÈÇÅ¿½¹·´¯­ª§¦§§¥Ÿ–‚qiikjkqtlh+ - - - - - - -  -    "#$%%$#'‹Ž‹ŒŠ‡†ˆ†…ˆE  - - - - - - - d•Ž‹‡jbeghllmjjx‹™¢©°µ¶·º»½¾¾»»½¾¼¹¹¸·¸¶µ¶·¸¸·µ···¹¹¹¹¹¸¹¸·¸¸¸···¶µ³³±¯ª§¢žš˜•‘‘‘’”œ–*%,)\Ä¿ÀÄÃÃÇ\)//}ÐÈÅɼE.MÃÌ«312:>;5.6^¡ÍÇÄÅÄÄÄÃÃÁÀ¾¼¼»ºº¹»»¼¾½ÀÄÅÇËÌËËÍÍÌÍËÊÉÉÈÈÉÉÉÊÈÆÂ¿»¸µ³±®«ª©¨¨§¥ ›xmjkkknuvml/ - - - - - - - - - - - -  - - -  !"#$$$%&&ˆ‰ŠŠ‰‡…‡‡„ƒC  - - - - - - - - - - - - k•Žˆodfgikklkjp} §¯´¶¸º¼½¾½»¼½½»»º¹¸¸·µµ¸¹¹¹¸¹º»¼½½¼½¼½¼»»»»º»¼»¹·¶µ´°®«¦¡ž™••–•‘œa%$**•Áº¾¾¿Çy076,nÉÈÃÇ]6?±È»H6G«º·³–b-3vÊÉÆÇÈÇÇÇÆÆÆÅÃÁÂÂÁÁÂÄÅÅÈÊÊËÍÍËÊÌËÊËÊÉÈÈÇÇÆÆÇÆÄÀ»·³°­ª©©§¨§¨¨¤¡œ“‚qlilklqwxnk1 - - - - - - - - - - - -  -  -"#$#$%&&‰‰‰Šˆ†„…„ƒ„? - - - - - - - - o–‘މ‚qcceggijjiiuˆ˜£­²µ¸¼¾À¿À¿¿¿¾¼»»º¸¶´³µ¸ºº¹»½¿¿¾¿ÁÁÁÀ¿¾¿¿¿ÀÀ¿¿½»º¸¸¸¶³²¯ª¦£ž›š˜–‘/!&!L´²¶¸»Ä2622,kÌÄÉn36›ÆÅ[1A¹ËÈÉÍÎ’8+eÉÉÇÉÈÉÉÈÉÉÉÈÇÈÉÈÇÈÉÊÊÊÊÌÌÌÌÌÊÊÊÊÊÉÈÉÇÆÅÃÂÁ¿»·±®ª¥¥¥¦¢¢¤¥¨¨¥Ÿš‘{mllnlnt{|pp5 - - - - - - - - - - - - - - -  #$##$%$'ˆˆ‡‡‡„ƒ„ƒ‚B  - - - - - u—“‘Š„vbccddfggigk~’Ÿ¨¯´º½¾¿¿ÀÁÂÁÀ¾¿½½º·´´¶¹»¼ºº¼¾¿¿ÀÁÁÀÀ¿¿¿¿ÁÂÁÀ¿¿¼½¼»¼¼º¹¶²¬¨£ ž˜œj.r!†¯¬²³¾-1RR10lÈˇ23ˆÇÈo25¢ËÅÅÄÅÐ6.ƒÏÆÇÉÉÉÉÉÊËÊÉÉÊÈÇÉÊËÊÉÊËËËÊËÉÉÊÊÉÉÇÆÂÀ½»»ºµ±­©¦¥¢Ÿ   ¢¤¦¨§¦Ÿ•ˆsonnnlou}|ot= - - - - - - - - - - - - -  -  !!"#&%%‡††…„‚ƒ€}}: - - - - - - - !™”‘‹†{ecc`adffeeiu‰™£«±¸»¼¿ÀÀÂÄÄÃÁ¿¾¼¸··¹¼¼¼½¼ºº½½¾¿¿ÀÁÁÁÁÁÀÁÂÁÀÀÁ¿¿¾½½½½»¸µ³®¨¤ ™—7XžEA§£§¬´)+^¬<4-xÏ–32qÉɆ03‡ÎÅÇÆÄÃÅY1E¼ÉÇÈÉÈÈÈÉÈÈÇÈÈÈÇÇÈÈÉÈÈÊÊÊÉÉÉÇÇÆÆÅ¿»¸·´²³­¦£ ŸŸŸœ››ž¡¤§§¦¥›“}npoonmpy€{qx? - - - - - - - - - - - - - - - -   "#$%%%……†ƒ~~|}4  - - - - - - - - %…™–‹…{hcc_`ccdeegk|ž¦¬²¶º¾¿ÁÃÃÃÃÁÀ½¹¶¶¸»¼½¿¾»»¼¼»¾¿ÀÂÂÂÂÁ¿ÀÁÂÂÂÁÁÂÁÀ¾½½¼»¶´³¯ª¥št!‚w}¢¤¬Š((RÑ/4,ª72_ÇÆœ56iÍÄÅÃÂÀÈs44™ÌÆÇÈÈÇÇÇÇÇÆÆÇÇÆÆÆÇÇÇÇÆÈÈÆÇÇÄÂÁÀ½¸µµ²²°­«¥Ÿœœ™š™™šœŸ£¦¦¥ ˜soqrrpnr…}szE - - - - - - - - - -   "$%$$$…„„‚ƒ€~}z|8   - - - &†–”†|hcba`abcdfgem|–¢§¯³·¹¼¾Á¿¾¾¾½»¹¶¶¸¹·ºº¹¹ºººº»½¾¾¾ÀÁÁÁÀÀÂÁÀ¿¿¿¿¿½»½»º¶³±­¬¥EO”ŠŽE>˜•š ƒ%#I±¶r'-1|;2N¼À¨?7PÀÁÀ¿ÁÃÇb21”ÉÄÅÆÆÅÅÅÆÆÆÅÆÆÅÄÅÅÅÄÄÄÃÃþ¾º·´²®®«©¦§¤œ™–•”’’‘”—šŸ£¦¥¢š“€lpssrppv‚ˆ|v|P - - - - - - - - - - - - - - - - -!#$$$%‚„ƒ€~~|}|{yz= - - - - - - - 'ŠŸ›–މ‚kcbb`_``cefedn†›¥¬²µ¹»½¾¼º¸¸¶¶··¶´³³´µ²´µ´µ¶·ºº¹ºº»¾¾½½¼½»¹¸·µµ²°®­¬«©§¦¤žž‡azvtYlЇv; Ÿ¦B %%*,<¯»±A2=´¾½½ÀÊŒ67>¬Á¾¾¾¾À¾¾ÀÀÀÀÁÀ¿¿¿À¿¾¾¾½½¼½¼¸µ²¯¬¨¥¡ Ÿœš•‹Šˆˆ‰Š‘•›Ÿ¢¤ œ“…roqsrqpr|†Š}z€Z - - - - - - - - - - - - - - - - -  !!"%%&‚„€~|{zz|{xz? - - - - - - -  +ŽŸš–’‹ofdb``abbdefghwŒŸ§­±¶»»»¼¹¶µ³³µ·¶³±­®­­­­­­¯±²³³¶¶¶·¸¸·³°°°¯«©¨¤¢¡žœ››˜–Žˆ‘D*|y~m-“’Ÿˆ# ##%1¥´±E-.¢¾¸»²0-&c³¯³±±³³³´³²±²³³²²²³³³²²°°¯­©¤¡Ÿžœœ˜—•’Š…ƒ~€ƒ†‰”™Ÿ¤¦¦£™Švmortrqqu€‹‹}~…d - - - - - - - -  - -  "%%%€}}|z{{zwv< - - - - - - - - 2‘𗓆thfdba`abdefhej“¢¨¯³¸¹¹¹¸¸¸¶¶¸¸¸´±®¨§¥¤£¥§©§©««««©«««§¢¢¡Ÿžœš™•’Š‹Šˆ……~pKyte*„ƒ„’c#Š¡ =^sfT80„–‘“—™››œœž £¢¡£¢£¥¤¤£¡ Ÿžš——”‹ˆ…„{{}€‚„‹•šŸ§««¨¢”‚qnprsrqsx„ŽŠ{€†g - - - - -  - -   #$$&}}~€|{z{xywuC - - - - - -  2’ž›˜’ˆ{jjhda__abfehhfr‰¦°´·¸¹¹º»½¼¼½½¾»¶²ª¤¡žœžœ›ž ¢¡ Ÿ  ž—ŒŠ‹†‡„ƒ{|{vxzussrxB$T[]\]^^L!Zja nsrsx?]rx1">gonsz{ƒ††‹‹Ž‘‘“”—™›žœœœš—”‘Ž‹ˆ…~}{|z{€ƒ‡”—ž¢¦­®¬¦šŠtmorssrru|‡”‹}ƒ…d - - - - -  - - !###'}}~}|zxwuusuE - - - - 2”Ÿš“‰{mllgcb`abachiho|‘¡¬²¶¸»¼½¿¿¾ÀÁÀÀ¿º´°©¢›š˜™š››››˜–“‹{yyywvtttrrqoolkje<59[kiiffffj]EC@\e@9:_iihhkI+*)Vfg?,1449AIS_hegjmqru{|y{z|€€„‰“—šœš˜–‘ŽŠ†„~{yz|}~„Š˜›Ÿ¤©¬¯®©¡“|mnpttssrv€Ž•‡€‡Œh - - - - - - -  - - - !##$'{|}|{yxturquP - - - - - - - 7— ž›–Šqqnleb_abcceikio‡œ§®´¶º½¿ÀÀÁÃľºµ±«¦¥¡ž›˜˜™š˜™˜”‘Œ„zvtutsrqrqnmmkhgea^]``^]^[^]^``aaa`^^_`cadeddcfcbb```_^`baaaababdfiknoqtutuuwzzz„Š‘•šœœœš—”‘ŽŠ‡„€~|zz|~‡Ž’™ž¡¦ª«°°­¦›smpqtuussy‡””„‚Šo - - - - - - - -  -  #$$&|zy{ywttssorR -  - - - - - - - - - - 9›¡ ˜’‹ƒvtumhea`bdcehjhiyœ§¯³·½¿ÀÀÃÄÃÂÿ½¹µ¯­¬¨¡ž›š›œ›™—“Œ„}wrrsqoponjijffggecdddefecddefgeddcbdcddcdedccabb````^^]^`^^_aaddeilmoopqqruwy{}€ƒ…ˆ–ššœš—–‘ŽŠ‰ˆ†‚~{|}}„ˆ‘”™ž£¦«­¯²¯­£”~lnpsstuuv–”„…Ž’u  - -   -!#%%{{z}vvtssrpmI  - - - - - - >¢¡˜“Œ†zuwrnhdabdcehiihmƒ”œ¦®µ»¾ÁÂÄÅÄÂÂÀÀ¿¾¹³¯¬§£¡Ÿž›™˜–’І€{wvrppommlnmmmoononnmmopmlmnmjjjhfghhhhghifefeddedbab```a`bccdfdejnpomoprsw{}~€ƒ‡Š”›žœ˜•“‹ˆ†…ƒ~}|~‚‰‘—œ ¥ª«®¯³²°¬žŒomprtutuvyƒ’™•‡’–x - - - - - - - - - -   -0!$%&yyzyuvssqpnnP  - - - - - BŸ¡¢ž™”‰}u{yrmgcbcdegjjlku…–¢©±·»ÀÂÄÂÂÁÀÀÁÂÁ»·´°¬¨¥££¡šš–’Œˆƒ|zwttrtvwxwwvvwvvuvtrqsrqpqqpponmmmnlmkjjikihghhgfddcccdcdeefghjmoponqsw{~‚ƒ†‹ŽŽ•š¡¡žœ˜”‰‡…‚€|}}„„Š”™ž£§ª¯±°±´²®¥–zkoqstuuuv~Š—œ”‰•˜y - - - - - - - -  * "#&&wxwvttrrollkW - - - -  J¡¢£ž›•މ€uz{uqleabcdfijkkm{™£¬³¸¼¿¿ÀÀÁÀÀÃý¹·³°«©¥¢ Ÿž›—”“ŒˆŠ‡‡„€€€„‡ˆ……‚‚‚€~|{yzxwxwuuttttrrrssrqnmmonmlllkihhhhiiikikmnpqsusrvz‚ˆ‰‘““•˜š›Ÿ¢¢ ™–‘ŽŠˆ†ƒ~|€‚†‰Ž–œŸ£¨«®±´´´´²«‰qmosutuvvz‚‘›Š“—šz - - - - - & "#&()ywttrrronlkhb - - -  N¢¢£Ÿ›•‹„vw|yuqkcbccegikkkr‘§®´¹»¾ÀÀÁÂÂÃÄÄ¿½º¶²®¬§¤¡ œ˜–”“‘“Ž“šŸœœ™˜™—•‘‹‹Š…„‚~}}|zzz{|zwvuvuvvuqsqqppnpqosttwy{ƒƒ„†ˆ–———˜›œžžŸ¢£¢Ÿœ˜“Žˆ…„ƒ€}{~‚†Š•›¡¦©«¯²µ¶··³­£‘tnoptvvwxw|ˆ˜œ‹Œ–˜{ - - - - - - - - - -  !' #&*)*ttrsrpmllkjfd - - -   P¦£¥¡›•ˆxx~|ysngabbdghjlmox‹— ª¯µ¹»½ÀÂÃÃÄÅÅÁÀ¼¹¶±®ª¦ œ™–””“‘”••˜™£ª±´´²±­®ªª§¥¥¦¤¦¥¤¡ž›œ™˜•””’‘“‘Žˆ†…ƒ‚€€€~~€‚ƒ‚‚„††‡ŠŽ‘™Ÿ¡¡ ¢£¤¤¥¦£¢£¥¤ œ—“Žˆ„~~|}ƒ‡Œ‘—œ ¦©¬¯³¶¹¸¹¸±§œ„lnqswvwyzzƒ›Ÿš‡™›› - - - - - - -  #'#&*,-.trsqonljjihec - - - -   N§¦¨¥ž˜“‰}v|~zwqkdaccdgjkkmr}™ ª²·º½ÁÄÃÄÆÇÅÃÃÀ¾»·²®¨¢™–“’“‘’“’—ž¤©±·»»»»¸³±±´µµ¶··¸·´²±°°®¬¬ª§¥¦§¦¥¢ž›š™—•”””•–“’’–—™šš™™š›œ›› ¢¢¥©««ªª«­­¬­«©¨¦¦¡™‘‰„||}~ƒ†‹’˜£§ª­±µ¸¹¹··³«ŸŠtmoquwuvyz‰•ž¡–„‘™›  - - - -$&!'*-.0sqrpmlkhhgddd - - - - - -  M¨¦¨¥ œ–‘‹€x|~~{umhbcbefijjlpvƒ˜¢¬´º½ÀÃÄÅÇÈÅÄÄÄ¿º¸µ¯¨¢˜”“‘“•žª±·¼¿À¼»¸¶º¼¾ÀÁ¿ÁÃÄÃÃÃÃÀ¾¾¼½¼º¶¶¸·¶¶²±®­«ª©§¦§©¨¦¦§¨©­¯±³¯°°¯¬¬­¯±²³³²±²²´´³²¯¬¨¨¥ –Š„€~~|}~ƒ†‹“™¤©¬®²¶¹ººº¶²­£–ynopsvwwwy|‚œ¢¢‘†•œœž - - - - - - - - -  &&"$+/02pnqmmkffffcac( - - - - - - - -   M§¦§¤ ž™•…y|~€{tneabdfgjjkou{‡”ž¦®¶»½ÂÆÆÇÇÄÄÇÆÅÃÀ½»µ°¬¦¢›™•’“™¡¨±º¿À½»»¾±±ª§§¥¥ž—˜—–”—¥·ÂÄÀ½¾¿¿¿¿¼»¼¼»º¸··¸º¸·¶¹¹½¸ª˜‡„Œ•£´¿½»»»»½½ºº»»»¹¸·³°®¬¥œ‘Š…€~}„…‰”˜Ÿ¤¨­´¶·»¼¼»¹´°§œ†nmpsvywvy|‰– §£‹†–Ÿ ¡~ - - - - - - - - -  -)(#!#(/45mmlhkjgeddcb`3  - - - - - -    S¤¨©¦¡™–’Šzy€ƒ€}xslb`bdfgjlnqv~Š“¥®¶»¿ÅÆÇÆÅÆÈÈÈÇÅÁÂÀ¼¸³¯«¨¦£¥ª²¹¼ÁÀ¿½»»¾¼RA=;;<;98<7476=K\¯ÆÄÀÀÀÀ¾¼½¾¾¾¾¾½½½¼»ÀÁ¨~X?320,.39KpžÂÇÀÀÂÃÄÃÂÁ¿½¼º¸¶´¯¥™„~}€†‹”™ ¦ª®³¸º¼¾¾½»¹²¬ tmmpuxywv{„›¤§¡ˆ‹™¡¡£z - - - - - -  -((&!#*468jliihhfccc`\[9 - - -   W¨ªªª¥Ÿ›™•‹~w€ƒ€{tpgba`cgijnoru}ˆ“𤝵¼ÁÃÆÆÅÆÈÉÉÈÇÇÈÇÄ¿»¹¶µ´µ¶ºÀ¿¾¼º¹¹¹»±?9:99::8;:6779;;5.@z»Æ¿½¾½¼½¾½¾¿ÀÀ¾½¿Á¢f:+01/430111,*7VŒ½ÆÀÁÁÀÀ¾½¼»¸·µ²ªœˆ€ƒŠ‘—›£§­±¶º½¿ÀÀÀ¾º¶­¡–}lnptwzxxyƒ‰–¡©©›ƒœ¡¡¢w - - - - - - -  -%)'"!!%17:jhffgdd`a_[WU= - - - - - [§©¬«§¡š–Žƒw~ƒ‚}wtjdb_`eginnprwŽ– ª³»¾ÂÄÄÃÄÅÈÉÈÇÈÉÉÈÆÅ¾¿¼¼½¾¿½»ºº¹¸¸¸»§63446776546::978874/JœÇ¼¼¼¼¼¼½½¾¿½ºÃµo6)021../210013793/M›Ãº¸¸¶´²°¯­¬ª¤›ˆ„„ƒ‚…’•𠦬³µ¸»¾ÀÁÀÀ¾º´®¥•„topsvzxwz}€ˆœ¦«©•’Ÿ£££x - - - - - - - - - - - - + ((% "%.6;gdcda``\XXVRQD - - - - - ]¨©¬«§¤¡›•’†y~ƒ„ƒ€|wrhb_^`eilnnqtz„™£ª´¸¾ÂÂÂÄÆÉÊÉÊÊËËÊÉÇÅÄÅÄÂÁÀ½¼»º»º··¸º0...0.0012/41,/3345731‰Æº»¼ºº¼½»»¸Á¥L///00010//./0/04421+1y»·²±­«¨¤¢žœ–†ƒƒƒ‚‚…‹‘”™Ÿ¥¬¯´¹½¾¿Á¿½¼¹µ¯©›‰wonrsy|yxz…‹–¡ª¬©‘ƒ–¤¦¤§p - -  %R6%' !$%*17aa`_]^]WVVSRPJ - - - -  cª©¬¬ª§¢Ÿš“ˆ}}„………zunfa^^aejlloru{„–Ÿª·¼ÀÁÂÆÉÊËËÌÎÍÌËÊÊÊÉÉÇÅÃÀ¿¾½½¼¹¶¶µµ•+)))$PŒŒ‘‘„pJ.(10/311’ø¹º»»¼»¹ÂŸ?0111/00),044.++2530...(k¸±®ª§£ š•‹ˆƒ€€‚…‰”šž£©¯´¹½¾¿¿¿¾½º¶±ª‹{opqtw{{{z}„‰œ¦®­¨‰‡š¤¦¥¥j  - - - 3M3$$"%%%,3___\[XXSSUQPLE - - -  c¬©­®¬¨£Ÿ›–Œ~ƒ‡‡†‚}xslea_``ehjloqv{…‹•Ÿ®·½ÀÄÈÊÊËËÍÎÍÌÌÌËÌËÈÇÈÆÃÀ¿¾¾¹·µ±¯®'&$%q³®±²µ²²´²“K&-,..1;§»µ¹¹¹»ºÂ¥=23/11,);]™ ¡šŒmG--0,,.,"q¯£¢ œ˜”ˆ„‚€‚…ˆŽ”›Ÿ¤ª°µº¿¿ÀÁÁÀ¾¼¹³«¢‘soqswz|{{|€‡Ž• ª¯®¤‰ž¦¨¦¨f - - - - - - - - - - - - -#(1G2$%!$%'-1]\[XWVTRQPMLHB - - - - -  e­«®°®ª¥ ˜‚~ƒŠŒ‡†‚|uphebb`bfhjlnsu{„Š‘ž«¹½ÃÈÊÊËËËÊÌÌÌÌËÊÊÈÈÊÈÆÃ¿¼¹µ³²°®¯Ž#$#$ p²­¯°±°®­²ºµc'-,--'Y¸´µ¶¶¸º¹L010.0++k§½¿ºº½¿¿Ã´:&,)&##$…¢™™•‘Їƒ€‚„†‰Ž•›¡¥©®´¸¾¿ÀÂÃÃÁ¾»·­¦›‡vopquy||||„Š‘™¥­±®˜Ž¢©¨¥¨^ - - - - - - - - - - - - &)*-@/!#"$%+1ZXVRRSROMLHDEB! - - -  j««°°¯¬§¢žœ“‡€‰Œ‹ˆ†€xsmhfdcbahikmqruy€†Ž™¬·¼ÁÄÆÇÉÊÊÊÊÉÊÊÉÊÊÈÊÉǾ»¹¶µ´±®²#$#%#t´­°³µ³°°´´·»]'.+,+-“º³³²³¼q,/-,.(5‹»¶´´¶¸ºººº»Ä«P"&!"?˜““ŒŠˆ†…ƒ„…†‰Ž•¡¦«¯³¸¼ÀÁÁÃÄ¿»·²ª|ooruxz|{|~ƒ‡Œ•Ÿ©°±«“£©¨§§W - - - - - - - - - - &())/A,"$!%%)0VSQONOLJIIFEC? - - - - -  fª¬±²°®¨£ ž–ˆ€ˆŒŒ‰‚~wpmifcbaaeimoostzƒˆš©´¼ÀÃÅÇÈÊÊÉÊÉÈÈÉÉÈÈÈÅÂÀ½º·¶¶²±²Œ$&#$#p³­±³´³´´µ¶´·­>'++,&]º±²±´Ÿ4**)+(5𷬮°±²´´³´´´²¶±Lz•Œ‹‰†…„…‡†ˆŠ”œ¡¥«¯³¹½ÁÂÃÄÄľº±ªžrmoswy}}||€…‹’›¤®±±¨†–¤¨¨§¥S  - - - -  - - - ((()(+7*$ $%'/SSOMLKLIFEDC?=& -  k«¬²³±®«§£Ÿ™Š€†ŒŽŽˆ„|vrkeddcaafklnqru}ƒŠ¬¶º½ÀÃÆÈÉÉÊÊÉÈÈÈÈÈÇÆÆÃÀ½¸¶µ´±³Ž$%$%$q²®²³´³´´³µµ³¹„'-+,*7§´²±·j#)(')&†¶«®¯¯°±±°°°°±®©ª›1P‘‰‡‡…„ƒ†‰Š”𡦩­²·»¿ÂÅÅÅÅÃÀ»µ«ž“ƒspoqvy{~~|~‚‰— ©¯±°¡}„›¥ªª©§N  - - - - -  ))()))+1*"!"'/QOMJJFGECAAA><. - -  p«­²²°¯­ª¦ œ€€ˆ‘‘Ž‹ˆ‚|wqiggfbbbeimpqsuzˆŽ—£­²¹¾ÂÄÆÈÈÉÉÈÇÇÈÈÇÈÉÉļ¹¶µ±³Œ$%$%#t³®°²³´´µ´³³²³®;++*+*‰¶®±­@"&&&!X°««¬¬­°±°¯¯®¬«§¤Ÿ£g4‡…„†…„…ˆŠ‹‘–š ¦ª¯¶¹¼¿ÂÅÇÅÆÂ¿»µ¬Ÿ•voprux{}~~|…Œ”œ¤­±´¯—|Œž§ª«§£E  - - - - - - *)(()((*3*#!#-MIGGHED@?@?=>:/ - - - -  r¨¬²³±°­«§£Ÿ˜‡‡Œ‘’Š…€{vojggeda`fhlnorsy~~‡‘¡­³¸¼ÀÃÅÅÇÈÈÇÈÉÈÇÇÈÉÇÅ¿½ºµµŒ$&&(%w¶±²µ¶¶·¹¸¶µµ´»Z$))+&m¹¯´—+&$$$+”°ª¬®¯±²±°¯®¬ª¨¤£Ÿš‰''€………„†ˆŠŽ‘”£¦¬±µ»ÀÃÅÆÇÇÅÄ¿»¶­¥•„uonqtwy}~~}~ƒŠ– ©¯³²«Ž‘¢«®¬¦=  - - - - - #)()(()))*/-" ".GFFDBAA?>>=<;73 - - - - -    sª«²´²¯««©¥ šŒ‚‚‹’“Œˆ†ztnihhfcbbegjmqsuv|€…‘¢«³¸½ÂÄÆÆÆÈÈÈÉÉÈÈÈÆÈÇÆÄ¿¹ºŽ%()*'~¹²²¶··¸»»¶¶µ³¹n())+'^¹²·~$'#%$Gªª«®®¯±°®­¬¬ª¨¥¢ ™‘’;'|‡ˆ‡ˆ‰‹Ž‘•œ¡§¬´¹½ÁÆÈÊÊÉÇÇÀ¼º²§™†wnmptx{}~}}‡Œ“›¤­²µ²¦…–¨¯¯¬§›; - - - - - - - #')(((*((''2- 'GCAA?;<<=;<;876 - - - -     s­¬²´´±­«ª¦£œ…‚Š“”’‘Œ‡„yrmjihgebcejmprruz|€„‘ «²·½ÀÂÅÆÅÅÆÆÇÇÈÉÉÉÇÅÄ¿¼¿(-+,+‚¸³µ···¸¸·¶´³²·z&)')'U±«±j $#%!_«¥©©©ªªªªª§¦¤¢¡™•‘“G%}Š‹‰ŒŽ”˜œ£§¬³º½ÂÇÈÉÉÈÆÃ¿»·°§›‹|tkortx{~~~~„‹— ©±¶¶±Ÿ~‡›©®®¬¨›4  - - - - - - $(('(')('''(0-$DB>=<988987875/ - - - - - - -   oª«²µ´³°­©§¦ –‡„Š’••”’‰†‚~wrnkjihfdegimppruxz{Š›¨­´¹½ÀÂÄÄÅÅÆÆÈÉÉÈÆÄÄÃÁ¾Å‰-/+-.Џµ¶··¸·µµ¶¶´²¹%)&(%M®¨°W!"% o©¤¨¨©©ª©ª©¦¤¢¡Ÿš–”Ž“I+…Ž‘•—› ¥«¯·½¿ÂÆÉÈÈÇÄÄÁ½¸±¨Œ}ummptw{~€~~‡Ž”›£®³·´¯˜|Œ©­®­©˜, - - -  - - - - - &('''%&&&'''(/+A?<<;999844543- - - - - - {©¬±´´³±®«¨©¦™‰„Š’˜˜˜—”‰†‚{upnllkjhgfgkmpqqtvxz„•¡«±·¼ÂÄÄÄÅÅÅÆÇÇÆÅÄÄÄÃÂÈ|-210/–¾¸¹¸¸¹¹¹¸¶¶µ²ºs%&$&#P¬¤ªP #"«§©«©««ª©¨¥£¢¡ž›–“’E3’“••˜œŸ£¦¬±¶¾ÁÃÅÇÈÈÇÅÂÁ¿»´«¢’ulmrtv{}~€€€„Š’™ §°¶¸³©‡{’ ª¯¯¯©' - - - - - - - - -(''&&'&&'&$%&%/0<>>=9866543420/ - - -    0ާ®²³´³°¯­ª©§„ˆ•˜šš—’Œ‰„ztollllljgghknqqrtuwyzœ¦±¼¾ÁÁÂÃÃÃÄÄÅÆÄÆÆÆÄÃÉo59:7:¦Áººººº»º¹·¶´±·Z!$#% Z¬¤ªQ!xª¦§§¨©©©§¥¤¤¢Ÿ™–“‘“=?˜•—™ž¡¥¨­³¹¿ÄÅÇÉÉÉÈÆÅÁ½º´¬¦—‡yrmpuwx|~€€‡Ž—ž¤«²¶·³¢‚‚–¥­°²°©Ž"  - - - - - (''''&&&&&%%%$&+-::::754420//.-) - - - - - - - -   T›¡­³µµ´±°®«©¨¢‘……‹’•˜˜—•’Œ…‚~ytommmnmkhiilpqrsttvxy~‰—¢¯¶»¾¿ÁÁÃÃÃÃÃÅÇÆÆÃÁÅ_89>=H²¾»º¹¹¸¸·¸¹µ°¯®?$$"$i«£«Zd©¤¦¦¦¤¥¨§¦¥£Ÿœ›˜–”—Š(Wœ—œŸ£¦«¯²·¿ÆÈÉÊÊÊÈÈÅÃÀ¼³«£—†zrnpuxx|€€„‹•œ¢©¯¶¸¶²—|†š¨¯²²¯ª…  - - - - - - - - !('&'''&%%%%%%$$$-0776753210.-,++* - -  {¡ ¬³µ´³²°¯®«©¢•Š…“•˜™š™•‘‹†ƒ}xsppnmnolkkknprsstuusuz‰™¦´º½¾¾ÀÁÂÀÀÅÅÄÃüO:9TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y- - - - - - - - - -  33-,,,++*+*+*)*)))++**)(()&(( - - -  - - -  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*3./3A­º¸··¶³³²°®¬«©§¦¦¥¢ž›—“‘‹Œl H˜”w!!ƒ¡ ¢£¤¢¢  Ÿ  Ÿ¢¦®´¹½ÁÃÀ¼»½½¸¬šŒŠ•ž¨¯³²²µ·¹º·³ª–©°³µ¶·¶¯©²¯£F  - ##$##%,,,/0.164.-,+********++*)))**(((+.0/4956:758;89=@9:;7 B±ÇÎÏÍÉÊÊÆÂ½»¹§—“˜£«±¶¸¸¹¸·¸¹»½¼ºµ®£›’‘˜š˜”‹†„‚€{x(/rrkW}{y%/ššœ ££¤¨œ3$&%'%c·±³µ¹¢1,.,14ƾÁÁÁÁÀ¿Å21,+0T·´µ´²±°°±¯®¬°®®®¬¨¤žš“‘‡†‹‘z$<”“%d¥¢¤¤¤£¡  ŸžŸ” ®°·»½Á¾¼¾½»³¤”ŽŒ‹Ž–£¬±±±²µ¹º»¹²¦‘’ ª²µ¶¶··µ«®¯¥‘…f - - - -$##$$&-/.11-/172-*-+*****,*)()*))***(),1-.11011345578<<==<5 V½ÇÎÎËÈÊÊÆÁ¾¼¸©š“™¢©®²¶¸º»º¸¸¹¼½¼¶°©Ÿ•Ž”›š—•’‹†…ƒ~}/'suyM#r}|}**„”•“”˜™›¤b  1›¯¬­®®³S&*(+*_¿¹ºº¼¾¾ºÁ}**)(*]µ¯°®®­¬¬«ª¨©s`b_[USKC<81`‘’‚)0Œ•Œ,:¢§¥¤¢ œœœœ JV­¸¶¼¿ÂÀ¼½¾½º¬™Œ‹Œ“ž¨°±±±³¶¹ºº¶¯ •£­´µµ¶¸¹¸­­°©™…z, - - %$$%%)-0/00-,-26.+,,**+**)**(**))))),+-../00//3355565799:95 - vÃÇÎÎËÉÌÉÄÀ¾½º¬”–¡©®²³·º¼¼ººº»¼¼¸°§¢›“‘–››™–’†„„…3"mzvu)E€…‰0*…Ž‘’‹,X¢¢¦§¨§®s$#'&5¡¸±±³¶¸¸¼r%%%#$T®¨¨§¦¦¤¤¢¢¢¡C!Q’‹/(„““@ f«¡¡¡ž››››˜¤w'$R«¿ºÁÀ½»¾¾¼³¤“ŽŽ˜£«±²²±³¶¸»¹³­œ‘—£­´µ¶¸¹¹¸°«¯­¡ˆ€Q - - - &$$%&*///0/-,,-45,++++++++**)))()*)*3/000.10038766777596987! &˜ÃÉÏÏËËËÇÃÁ¾¼º¯ –•ž¦¬°´¶¸»¼º¹º¼¼¼º´«¢–’’˜š›™—•ކ‡‡2!o~{}\f‚ŠŽ3+‡ŽŒ‹ŒŒ‘\$|”—›žŸžž’* #j±§ªª«ª«±m""C¥ žžœšš˜™œ›> T‹Œ’4!}‘•_ '~ª Ÿ››š™— ”1"'$D¾¿¾º¼À¾¹¬˜ŽŒ“§¯±±±²´·¸¹·±¨—˜¥­³·¶¸¸¸¸°¨¯±©’€r - - "&$$&(,//0/.--,-.72++++)*(*))))))))**6324301128;667:88:=8:9:( - =¯ÄËÐÐÌËÊÆÃÀ¾½»²£˜”œ£ª¯´¶¸¹º¹¸¸»½¼¼º°¤›—’“•˜šœš˜•‹‹8"sƒ€~32„‡Œ‘1*„‹ˆ†…ˆ…25‚”˜œŸ¡šžQ2™¢¢¢ ¡¡¤o/Ž“’‘Ž‘–˜“7_ŒŒ’: y’•„'#.~§žšš™– œ='&(&~û¸¹¿Á½¶¥“Ž˜¢«±²±±³µ·¹¹µ¯¥–‘𥮳¶·¶¹¹¸´«°³¬œ€„5 %''&&),...-----,,091,,.,*)**())))()++<8798549<9:6::<<=AB?@A@7 -  f¹ÄÍÏÎÌÍËÄÂÀ¾¾¼´¥™•›¡§®²¶¸¹¹¹¹¸»¾¾¾»³©—“‘‘’•˜š›š—•’’:!x‹‡ƒŠa^‘“–‘0(€Ž‰‡ˆ‡Žd(6@JOT[`cIh¡—–—™˜›z]‡‚„…‡ˆ‘”–’1_ŽŒ7 v–•žK! )qœ¡›ž¡‹>&%&%9®¼µ¶»Á¾¸­š”𦮲²²²´¶¹º¹¶­£’‘œ§®²µ¶·¸ºº¶­¯³°£‹ˆ[ ((*))+,///.-,-.-,,27.,,-,++*)))))()**<<=>AB@CEEFEHHGO - 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“𢍮²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘І}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(;<DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnЇ+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k - *()((&).0/.-././12:82270,++++++++*))*+BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’𥬲¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+SQRSRQPWWWRQQTVUSRTUWVSUTTW9 - PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—𣍬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**++[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄÆÂ¹±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†M00.,,00//.-,-./-+,-/0372..--,*,-051-)*PRRWPPRSSTX[RPSRSQROLNPRQPPPNƒÐÌÌʹ¯¼ÉÆÂÃÌÔÖÖÕÒÍÇÅÆÇÍ×ÚÓÆ²š”’˜¢ª±¶»½¿¾¾¾¾ÁÁÁ¿½¹·³­©¦£  ¡ ¡¡¡¢ žœœœœœžžž¡¢¢   ŸŸœ˜–“”••”’‘‘’•˜œ¡¦©®²·º¾ÁÂÃÄÄÄÄÃÂÀ¾½¾½»¹·¶µ´´¶·µ³¯ª¢™ˆ„ƒ€€ƒ„ƒ…†††ˆ‰“™¤¨«®´»ÁÅÇÆÃÁÅÇý²¦›””””‘“Ÿª´¶¶¶µ´µ¸½ÁÄÄÀ»µ¨™Š„‡” ¨¯³µ·º½¾½»º¹¹º¼¼´±³µ¸¹ºº²žŠg/31,,11//0/-,...-,,..132/.--+,,-,/3.+*TXSTQQSTWVVXPNRTTQSSPNOPNOQNT0 C»ÌÌËŵ±ÀÉÇÂÂËÓÔÕÕÐËÆÃÄÇÌ×ÛÕȶ–’”¥­´¸º½½¾¿¾¿ÁÁÀ¿¾¼»·³®«§¤¤¢¢££¡ ¡  ŸŸ  žž ¡¢£¢¢¡¡¢¢¢¡ ›˜–••––—˜™š ¡£¥ª¯³¶¸»¾ÀÂÃÃÃÃÁÁ¿¾¼º·¸¶³±¯®®®¬«¨¨¦¡œ–‡€€ƒ…„‚‚…„†‹‘•𤍫¯´ºÀÆÉÇÃÃÄÅÄ¿´§““””’‘—¥°µ¶¶µ´³·½ÁÃÿ¹°¡‘…ƒ‹™¤ª®´µ¹»¼½½»¹¸¸»½¼±°´µ¹º»ºµ¥‹Œ~3*1+,/.////.-,-/-----./01-,,--,,+.030*UXSRSRWXZVVXPOQSTRPQRQQOOQPPY@ }ËÉÌËŲ²ÆËÈÄÀÉÓÕÕÔÐÉÅÃÅÈËÖÜÕʺ¢—“Ž‹˜¡ª¯µ·»½¿¿À¿ÀÀÁÀÀ¾½º¶³­¬«§¥¥¤£   žŸŸ Ÿ  Ÿ¢£¤¦¦¥¤¥¤¤£¢¡Ÿ›šœœ››œ ¢£¦ªª¬°µº½ÁÁÂÂÂÃÂÂÁ¿¾½º·¶³²³°­ªª«ª§¢ ›˜“‡„ƒ‚‚€~ƒ…ˆ’˜›ž¢¦ª¯´»ÁÅÉÈÄÂÆÇž¶©–’“”’• ­´¶µ´´³µ»ÀÁÃÄÁ¿¸«›Š‚„‘Ÿ¨ª®³·º»¼¾¼º¸¸¸»½º±²¶¶¸ºº¹¶ªˆŽF"//-.00.-..,-./.-,+-/02/,+,*+,,./47.TSQQSVTRWSUUPPPQPONPQSSSTUSTYM.«ÊÊÌËÀ¯µÆÊÈÅÁÇÓÕÖÔÏÉÄÂÄÇÍÖÚÕ˼¤–”‘Ž‹Œ’¦¬´¸»¼¾¿¿¿ÁÀÀÁÁÀÁ½¹¶±¯±­©¨§¦¥¢¡žžŸŸ ¢¡¢££¤¥¤¤¤¤£¤£¡¡ŸŸ¡  Ÿ¡¢¤§ª®±´·¼¿ÃÅÅÄÄÂÂÀÀ¿½º¸¶´±¬ª¨¥¤£¢£ œš˜Šˆ…ƒ‚ƒ€}}~‚†ˆ‹’–› £¥¨ª¯¶»ÂÇÉÉÅÂÅÇÄÀµ¨›—”“““Ž“žª²¶µ´³´¶º¿ÂÄÄÃÀ¼µ¥‘…„—£©«®³¸¼¾¾¾»º¹¶¸½½º¯²¶·¸¹¹¹¸®”†Ž_.,,-/.----,--.-,++,.00,*++*,,,,/42RPMRUSPQSTSRSPPONRSOPQRQRTQQQS)eÉÉËÌʼ­·ÆËÈÄÁÅÑÕÖÔÍÇÃÃÄÇÍ×ÛÖÍ¿¨™•’ŒŒ˜¡¨°¶º½¾½¾¾ÀÀÁÁÁÁÁÁ¾º¶µ´±­¬«ª©§¥£ ŸŸŸ ¢¡¡¢¢¢  ¡¢£¥¤¤¢ Ÿ ŸŸ ¢¢¥¥¨ª®±µ¹»ÀÃÄÅÆÆÅÄÃÄÂÁ¿½»·µ³±°®©¦¡žš“’‹„}|}„ƒ}~ƒ‡‘–𢤧©¬°µ»ÁÇÊÊÅÂÄÅü²¨œ”“””“’œ§°´¶´³²µ¹¾ÁÂÄÿ¼²žŒƒ‚Žœ¥«¬­³¹½¿À¿»¹¸·¸½½¹¯µ··º¼¹º¹±žŠŠv$,-,../...,,,-..-*+-/.++**,-,+++.4SPQUTRVVSSTSTQQRQSQOQNQOQROOOQ=(œÇÇÊËÆ¹«ºÆÊÈÄÀÃÎÒÔÒÌÇÄÃÅÇËÔÚ×Ï«œ–•’Œ“›¤«²¶¹¼¾½¾ÀÀÁÁÀÂÂÁ¿»¹¸¶´´²¯­¬¬ª¦¤¡ ¡¡¢¡    Ÿ¡¢££££¢¡ ¢£¡¢¡£¥©¬®²µ¸»¿ÂÄÅÇÇÈÆÅÅÄÃÂÀ½º·´²°®®­«§¢›–‘Žˆ†~~……ƒ€„†‹‘•–™œ £¦¨©¬°³º¿ÆÈÉÅÂÃÅû±ªŸ–““”“ŽŽ—£­³µµ²²´¹¼ÁÂÂÂÁÀ½¹®š‰ƒ‡•¢¨¬¬­²¹½ÀÀ¾¼¹·¶º¾¿¸°³¶·º¼»»ºµ¤Œ…€=).////0..----+++,-.,*,,*,,*,,*,4ƒ‚…ƒ€‚ƒ€€€‚„…‚~|~€€€€€€€€‚ƒ„ƒ€€‚ƒ‚‚‚‚€€€€„†{|}}}~~||}~„ƒ€€~ƒ‚€ƒ‚‚€‚‚€ƒ€€€€‚~€€€€~‚‚}~‚†‚~|~‚~ƒƒ‚‚ƒ‚‚‚€€‚‚€‚ƒ€€‚€€€€€€€‚€€€€€€€€‚‚€€‚‚‚‚ƒƒ‚€€‚ƒ…‚zwy{{zz{zzyx‚ˆ€€€€€€‚‚€€€€€€€‚ƒ‚€€‚ƒ€~€€€€€€€ƒ‚‚„„„}~…~ƒƒ„‚‚ƒ„‚€€€€€ƒƒ„€‚ƒƒ‚€€€€‚‚‚‚‚‚ƒ‚€‚‚€ƒ„ƒ‚‚‚‚‚‚‚‚ƒ‚‚€€‚‚……‚‚€ƒ‡€‚‹ˆ…€€€€€€€€€€€€€€ƒƒ‚€€€€€€€€€ƒ€~…ƒƒ€~ƒ„…………ƒƒ€€ƒ€ƒƒ‚€‚€€€€‚„ƒƒ„„„ƒ€€‚„ƒ‚ƒ„ƒ€‚ƒ€€‚‚€€€‚†…€ƒ„ƒƒ‚€…†ƒ€ƒ‚…†„„ƒ‚€~€€€€€€€€€‚€€‚‚€€€€€€€‚‚€„‚ƒ~€‚€„ƒ‚‚„ƒ‚€„ƒ‚‚‚€‚€€€‚‚‚€‚ƒ„ƒƒ‚€€ƒ‚‚‚€ƒƒ‚‚‚ƒ‚‚€‚ƒ‚€‚ƒƒ‚„‚€ƒ„„ƒƒƒ€€€‚‚wy€…††ƒ‚€€€€€~€‚€€€€€€‚€‚‚‚€€€€~€€€€ƒƒ~€‚ƒ†„‚€€‚ƒƒƒ‚„„ƒ„‚‚‚‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚ƒ……ƒ€€‚‚‚ƒ‚‚ƒƒƒƒ‚‚‚‡ƒ‚‚ƒƒ„„‚€~€…†€~z|‰†‚€€‚‚ƒ€‚€€ƒƒ€€€„ƒ€€‚€~~‚€€€‚‚€€€‚„ƒƒƒ€~€€‚ƒƒ„ƒ‚‚‚ƒ„„„ƒƒƒ„ƒƒ††ƒƒ„ƒ‚‚‚ƒƒƒƒ‚‚„‚‚‚ƒ„ƒ‚ƒƒƒ€‚‚‚€€‚‚ƒ‚ƒ‚‚‰„‚ƒ…‰‰……„ƒ†…€„‡ƒ}†…€‚‚€‚‚€ƒƒ‚‚€‚‚‚„„€€€€‚‚‚„‚€€„†„…„€€ƒ‚ƒ…ƒƒƒ‚‚ƒƒ„€„„ƒ„„‚„ƒ€ƒ‚‚ƒƒ‚€€ƒ‚ƒƒƒƒƒ‚ƒ„ƒ‚‚„„‚‚ƒƒ‚‚І„ˆ…ˆ‡…„ƒ€…‚††„€ˆƒ‚„ƒƒƒ‚€€€€€ƒƒƒ‚ƒƒ€€‚‚‚‚€€‚‚‚€‚‚‚ƒ‚‚‚„‚„„€€‚€€€ƒ†„‚ƒƒ†…ƒ‚‚ƒƒƒ‚‚ƒ„ƒ‚‚ƒ‚‚ƒƒƒƒ‚„„ƒ‚ƒƒ„„‚‚ƒ„…„ƒƒ‚ƒ‚‚‰„€€„‰‚‚ƒ€†‚†„‹‚„ƒ‚‚€€€€€€‚‚€ƒ‚‚‚‚‚‚ƒ‚€€€‚‚ƒ~ƒƒ€€‚…„‚€€…„„ƒ„„ƒ‚‚‚‚ƒƒ‚‚………„…‡†‚ƒ‚‚€€‚ƒ‚‚ƒ‚„…ƒƒƒƒ€‚ƒ‚ƒ…„ƒƒƒƒ‚ƒƒ„‚‚ƒƒ‚ƒƒ‚ƒƒ‚‚‰ƒ„‚‚€}‚‚‚‰ƒ€††ƒ„ƒƒ‚€‚‚ƒƒ€€‚‚ƒƒ‚‚‚‚ƒ„ƒƒ‚‚‚‚‚~€†„‚„‡†„ƒ‚€‚ƒƒ‚ƒ‚ƒƒƒ‚€††…†…ƒ‚„‚‚‚‚‚‚‚„ƒƒ„ƒƒ‚€‚„„ƒƒ‚‚ƒ„…„ƒƒƒƒ„ƒƒ„„ƒƒ„ƒƒ‚‚„…Š„ƒ„„€€ƒ}‚‚ƒ‚‰„ƒ„‚„‰‚ƒƒ‚‚‚€€€€‚ƒƒ€€‚ƒƒƒƒƒƒ„ƒ‚€€€€‚ƒ~ƒ‚‚ƒ……‚‚‚ƒƒ„„‚€€‚ƒ‚‚‚€€„‡‡‡†…„„‚€‚ƒƒ‚‚€‚‚ƒƒ„„ƒ„ƒ€‚‚ƒƒ‚ƒ‚ƒ„ƒ‚‚ƒ‚ƒ„„ƒƒƒƒ‚„ƒ‚…‹„‚ƒ„„ƒ‚ƒƒ‚„„Š…„„‚ˆ‡‚ƒ‚ƒ‚‚€‚ƒ‚‚ƒ‚‚‚‚€€‚‚‚ƒƒƒ‚ƒƒ‚‚‚‚€€‚€€€€„…††ƒ€€ƒ„ƒ†…„€€‚ƒ‚‚„…ƒ€€ƒ†‡…†„ƒ‚ƒ‚„ƒ‚ƒ‚‚‚ƒ„ƒ‚‚ƒ„ƒƒƒ‚ƒƒƒ‚‚‚‚ƒƒƒƒƒƒ‚ƒ„ƒ‚ƒŠƒƒ…†ƒƒƒƒ„„…†Œ„ƒƒƒŠƒ„„‚‚‚‚‚ƒƒ‚‚„„‚ƒƒƒ‚‚ƒ‚‚ƒ„ƒƒƒ‚‚‚‚€€€€€€€€‚††…„‚„…‚†„ƒ‚‚€€ƒ„„‚‚‚„……†ƒ„ƒƒƒ‚‚……‚‚ƒƒ‚‚ƒƒ‚‚‚‚„„„„……„‚ƒƒ‚„…†……„„„„ƒ„Š‚ˆ‡‡‚„ƒ‚‚ƒƒ„„‚†‰…††„„ƒ‚‚‚€€‚‚‚ƒ‚‚‚ƒƒƒ„ƒƒ‚‚ƒ‚‚‚‚€€€€€€€€€‚††…ƒ‚‚ƒƒ„ƒ„…„‚€‚‚€‚ƒƒƒƒ………†‡‡…ƒƒƒ‚‚ƒƒ„‚‚…„‚‚„…„„ƒ‚‚‚‚‚‚ƒ„ƒ„……„ƒ‚‚„„„„ƒƒ„……„„‚„Š€…‡ƒƒƒƒƒƒƒƒ‡Ž…ƒ‚ˆ„‚…†…ƒ‚ƒ‚‚‚€‚‚‚‚‚‚‚ƒ„‚€€‚‚‚€€€‚‚‚‚‚€‚…‡†„„‚‚†ƒ‚‚€„„…ƒ€€€‚‚‚ƒƒƒ……†…ƒ„„‚ƒƒ„ƒ„ƒƒ„„‚‚‚„„ƒƒ‚‚‚ƒƒ‚ƒƒ‚ƒƒ‚ƒ„„„……ƒ„„ƒƒ„„„„„…„ƒ†Š‚…†‚ƒ„„……„„ƒ†Œ„‚…‰ƒ„ƒƒ‚ƒ‚ƒƒ‚€‚‚‚ƒ„„ƒƒ‚‚‚‚‚€‚‚ƒ…„ƒ€„ƒ„ƒ‚‚‚€‚ƒ‚ƒ‚ƒ‚€€€€€ƒ‚‚‚‚……†„ƒ…†…‚ƒ„‚ƒ‚ƒ„ƒ‚ƒƒ‚„„ƒƒƒ„„„ƒƒ„†…„„„„„…„„„…„ƒ„„„„„……ƒƒŠ‡‡†‡‡†…†‡‡†„‡Œ„‰ˆƒ…ƒ‚‚‚‚ƒ‚„„‚€‚‚‚„…„ƒƒƒƒ‚‚‚‚‚€€€€€€‚…ƒƒƒ‚‚€€ƒƒƒ‚ƒ„ƒ€ƒ„ƒ„„ƒ‚ƒ……€‚ƒ€€€€‚‚‚ƒ„ƒ„……ƒƒƒƒƒƒƒ‚‚„…„„„ƒ„„‚‚ƒƒ„„„ƒ…†„„„ƒ‚€€€„‡ˆ†„………„„‚‚†„„………ƒ‚‚€‚€€‚ƒ‚‚‚„ƒ‚‚ƒ‚ƒ„ƒƒ‚€€€‚€€‚„„‚ƒ‚‚€‚‚ƒ‚€€‚‚‚€€‚ƒ‚€€€ƒ‚€€€ƒ„‚€€ƒƒ„…„ƒ„„ƒ„„ƒ‚ƒƒƒ„ƒƒ„„„„‚‚ƒƒƒ„„ƒ‚ƒƒƒƒƒƒƒ„~{zz||}€~}|~€…Š‚ƒƒ„…„‚‚‚€‚‚‚‚‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒ‚‚€~€‚‚ƒ€€€‚„ƒ‚ƒ‚‚‚€€‚‚‚€€‚ƒ€~€‚ƒ‚ƒ…„„ƒ‚‚„…„ƒ„…„„…ƒƒ……„ƒ‚‚ƒƒ„„…†…„„ƒƒ„ƒ„ƒƒƒ„„ƒƒ„……„„……„ƒ‚zz~€…‡††…†‹„„„„ƒ‚‚ƒƒ‚‚ƒ„„…ƒ‚ƒƒ‚ƒƒ„„„ƒ‚‚‚ƒ‚€€€€€€€€€ƒ††€ƒ‚€€€€€‚ƒ„ƒ„‚€‚€€€€‚‚‚‚ƒ„‚ƒ…‚ƒƒƒƒ„††………„ƒƒ„„„„ƒƒƒƒ‚ƒ„…„ƒƒƒ„††……‚ƒ„„…†………„……„ƒƒ„…„„…„ƒ{{{}‚†…‚„„„ƒƒƒ‚‚‚‚„„……ƒ‚ƒ„ƒ‚‚ƒƒ„ƒƒ‚‚ƒ‚€€€€€€~€‚„‡†ƒ‚€€€‚‚‚€‚ƒ‚€€€~ƒ…„ƒ‚ƒ„„„„„„„ƒ„…„„…„ƒ„ƒ‚‚ƒ‚ƒ„…„‚‚ƒ………††ƒƒ„„„„„††…„‚ƒƒƒ„„ƒ„………„…ƒ‚}{~ƒ………„ƒƒ„ƒƒ‚‚„……„„„„‚‚ƒƒ„„„„ƒ‚€€€€‚‚‚€€ƒ…†ƒ€€€‚€€ƒ€‚‚‚€~€€~€~€}ƒ‚|‚„„„„„„ƒ„ƒƒ„„ƒ‚ƒƒƒƒƒƒƒƒƒƒ„„‚‚„†…„…„„„…†„‚ƒ„……„ƒ„„„„‚ƒ…„ƒ‚‚„…„ƒ„†‡†…„ƒ„ƒƒƒ„„„ƒ„…„…„„„ƒ„ƒƒƒƒƒƒƒƒ‚€€€€€€‚‚€‚ƒ‚ƒ~„……ƒ€€€€‚ƒƒ‚ƒ€…€‚„…‚‚€€€€ƒ€€‚}|‚„……„ƒƒƒƒ„„‚‚ƒƒƒƒ‚ƒ‚„„ƒ‚ƒ‚‚ƒ„„ƒƒ„…„………„…„„„„ƒƒƒƒƒ‚‚‚ƒ……ƒ‚‚„„ƒ„„„…„ƒ‚‚‚‚‚‚‚ƒ„ƒƒ„„ƒ„ƒƒƒƒƒ‚‚‚‚‚‚€€€€€€~€€€‚„ƒ‚€€„ƒ„‚€‚‚ƒƒƒ‚ƒ‚€€ƒ‚‚‚ƒƒ€€‚€‚€€€}}‚ƒƒ„„„ƒ‚‚ƒ„„ƒƒ„ƒƒ‚„ƒƒƒ„„ƒ‚‚„ƒ„ƒ„„„ƒ…††……………ƒ‚ƒƒƒ„„„ƒ„„„„„…„„„„„ƒ„……„„„„„ƒƒ‚‚ƒ„„…„……ƒ„ƒƒ„ƒƒ‚ƒ„ƒ€€€€€€€€‚‚‚‚‚‚}ƒ…‚‚‚‚‚‚ƒ„ƒ‚€€‚ƒƒ€~‚‚€€€€€~~{|}ƒƒ…†…„ƒƒƒƒ„ƒƒ„„„ƒƒ„„…ƒƒ‚‚ƒ„„ƒ„…„„…„‡‹†ƒ„ƒ„„„††……………„„„ƒƒ„……„„……††…†………„„„…ƒ„…„………†…„„…†…„ƒƒƒ‚‚‚ƒƒ‚‚€€‚‚€€€‚‚}„„ƒƒ‚ƒƒƒ…ƒ‚€‚ƒ€‚„ƒ€€€€€€~‚ƒƒ‚ƒ„†‡†…„„…„„„„„ƒƒ……„ƒƒƒ„„ƒƒƒƒ„„…†„‡Š„ƒ‚ƒ„…†Š‹Šˆ„„‰‹Šˆ‡†††‡†„„‡Š‰‰ŠŠ‰‰ŠŒ‰…„ˆ‹Šˆˆ‰ˆ‡„‚ƒ………„ƒ‚‚‚‚‚€€€€€€€…‚€‚‚‚ƒ„ƒ‚ƒ‚‚€‚‚‚€ƒ€€ƒ}}€€‚†‚‚‚„„ƒ‚„†…„„„„ƒ…„„„„ƒ„†…„ƒƒƒƒ‚‚ƒ…„……„…†ƒ„ˆ‰‚}ˆ‰†…‡‰Š‰‰‡‚ˆ‡‚€‚ƒ‡ˆ‚€ƒ„†…ƒƒ‡‹‡ƒ„„„„ƒ‚‚‚‚ƒ‚‚€€€‚€‚~‚€‚ƒ€ƒƒƒ‚€€„…~€|{€€€ƒ€‚„†‚€€€€„…„‚‚ƒƒ„ƒ„…††……„„„„„…„„‚„…ƒ„„…†‡ƒ‚‚…‹Š…|~ƒƒ„‡…}€€€€‰…€…ƒ~z{|}|„€|€~|}ƒŽ‰ƒƒƒ„ƒ‚‚‚ƒƒ€€€€€€€€„‚€‚ƒ……ƒ‚‚‚‚‚€€€{ƒ€~}€€€€€€€€‚ƒƒƒ„€€ƒƒ…†„ƒ‚ƒ……„„…„„„„„……„……„„ƒ„„„„„„…‰„ƒƒ…Œˆ‚|€„………Š…{|}{z{€‚…‚„|~~}{|€€~~„|x|ƒŽ‡„ƒ‚ƒƒƒƒ„‚€€‚‚€€€€€‚€€‚ƒ„…‚ƒƒ‚‚‚‚€sz}}|€~€€€‚‚ƒƒ€ƒƒ{~„ƒ‚ƒ„ƒ‚„„„„„‚ƒƒ„…†…ƒƒ„ƒƒ…„ƒ„†„„„‰†‚†Š‡}|…ƒƒƒƒ‡…‚ƒƒ‚‚ƒ‚„„‰‡‚ƒ‚‚‚ƒ‚~ƒŠ‚ƒ‚{|Љƒƒƒƒƒ€€€€‚‚€€€€€€ƒ…‚‚„ƒ‚‚‚ƒ‚rsx|}}€~€€€~€ƒ‚€~yw‚ƒƒ„ƒƒ‚ƒ…†ƒ‚„„„„††…„„ƒ‚ƒ„………†…„ˆ†„„‚}|…„ƒ‚‚‚‡‚ˆ†ƒƒ…„„„…„…ƒ††ƒƒ‚ƒ„„……€€…†‚……~~ˆ‰ƒ„„ƒ‚€€‚ƒ€€€€€ƒ‚€€‚ƒ…ƒ„„‚€ƒ„‚sqvy|‚‚€~€€~€}}||{z‚ƒ„„…‡‡…„…††…†…„…†††„„„„ƒ„†…††…„†…€~€…†„ƒ„„„„ƒ…ŠƒˆŒˆ‰‰‰‰‡…ƒ†ƒƒ†‰‰ˆˆ‰‰‡ƒ€€…†ƒ„…ƒ„~‰†……‚‚€€€‚‚€€€€~€‚€~€‚„ƒ„„…ƒ‚ƒsqsux€‚‚€€~~~~‚}{}~}~|}ƒ…………†††††‡‡†††‡‡‡†……††…„…„…„ƒƒ‡…€ƒƒŒˆ‚…†……„‰‹…†ˆ††‡ˆƒ‚‡ƒ€‚„„……†‰…€€‡‡ƒ„„†„ƒˆ‚‚„„‚‚‚‚‚‚€‚‚‚€€€€~|~€€€„ƒ€ƒ…ƒ€psuvv{€ƒ|{|}~€||‚‚ƒ„„„„…‡†„„……†‡‡‡……„…„ƒƒ…„ƒ…„„‰„€„ƒƒˆƒ…‡†…ƒ‰‰}~~}}‚‚ˆƒ~}~}~~…ƒ€…‡ˆŠ‹‰…†‚ƒ‚ƒƒ€€€‚‚€€€€€€€€~{{€€€‚€€ƒƒƒ€|osuuuv{€~‚€~{z|}|{~|z€‚„ƒƒ„…†…†‡…„……………†‡‡††„‚‚†‡†…„…‡„€€‚ƒ„„††…„„‚ˆˆ„…ƒ~}€€ƒ…~€€‚€~„‡„‚…‚€‚‚‚‚‚€€€~|~‚ƒ€€ƒƒ‚ƒƒ€}rqrsxwxz€‚~~}|{{}~~~}|z}‚ƒ…„…††‡†††…„„†…„†‡††……††„…†††……†€€‚„„‚‰……††‰‡„†††‡†‡‡††„ƒ…„„…„„„ƒ‚ƒ„€€€‚ƒƒƒ‚‚‚‚‚ƒƒƒ‚‚‚€€€€€€€~~€‚„ƒ‚}}€„…„ƒstsuwttv|ƒ}}~}~}}|{}~|y|ƒ„„…†„ƒ…‡†…‡†…„ƒƒ………………†‡†„ƒƒ…†‡…~€‚†……ƒˆ‰………†ƒ‚„…†‡ˆ‡†‡‡„‚…„„„„…„„ƒƒƒˆ…€ƒ„…ƒ‚‚ƒ‚‚ƒ€€‚ƒ‚€€‚‚ƒ€€~|}}‚‚€~~~~€…„~€uvvtsux{z|~|}}}|~~||||}|y{„†…ƒƒ‚‚…††‡‡†…†‡†………†…„…†…„„ƒƒ…ˆ…ƒ„†ˆ††ƒŠ††„„‡ˆˆˆ‡†…ƒ‚‚‡†…„„…„„ƒ‚„Ї‚„ƒƒ‚ƒƒ‚ƒ‚‚‚‚‚‚‚ƒ€€€€ƒ€||~~{|}~ƒ‚€€€~~€€€ƒ…€|}€vsstw{{}€~}~~~{||}|}~}}~{y‚…ƒ‚„…………†……„………†„‚‚‚‚ƒ„„„„ƒƒ†…‚€‚…†ˆ‡†„ƒ††…‚~ƒ„„…†ˆ†‚€€„†ˆ‰ŠŠ‹‰„‚…‹…‚„ƒ‚‚ƒ‚ƒƒ€‚€€€€€€~~}{{}}{}~€€€‚~~}~{~utuy}€~~}€~||~~~~~}}z‚†„…††††„„…††‡†„††ƒ‚ƒ‚ƒ„…†„„…„……ƒ„††‡†ƒ„„…‚~~~€ƒ†€„‚€ƒ…„‡‹ƒ‚…‰„‚„„„…‚‚ƒ‚€‚€€€€€€€€€€~~|}ƒƒ‚~~}}€~~su{~‚€~~~}~~~}}€~}|}~„†ƒ„‡†…………†‡‡‡‡††„‚‚„…„„‡ˆ‡‡††‡‡†††………ƒ„ƒƒ„……„‚€€†…„…€€€€ƒ‡„‚€„…„ƒ„ƒ€‚‚‚ƒ‚€€€€€€€€€€€}|€„„€€€~„„‚€„ƒvzz|}|}‚}{|}~|~|{}~}~~{‚‡ƒƒ†‡„ƒ„„………†……„‚ƒƒ„„…†††‡†…†‡‡††‡‡‡†…„ƒƒ„„†…„††„„†„‚„…„„ƒ‚€‚€‚„…ƒƒƒ„„……„ƒ€€€€€‚€€€€€€€‚}€€€~}~€‚€€€€ƒ„ƒ‚€€‚†…}}{|€€||y}~}}~}||}}~~z€…ƒƒ„…„„„…‡†„„ƒƒ„ƒƒ„…†††‡ˆ†„„„…„„„ƒ„„„……ƒƒƒ„„„…††ƒƒ„„ƒƒ„……„ƒ‚ƒ„„…„„„„……„ƒƒ„……„‚‚~‚ƒ‚ƒ‚€€‚‚€€}~€}~€ƒ‚‚€€~€‚„~€€‚„„€~‚~|z{~~}}~~~~|}{~ƒ„†††††‡‡†……„„……„…‡††‡‡‡…ƒƒ„……„ƒ‚‚„„…„ƒ„„„…„„„„„…„ƒƒƒ„„„„„„…„„……††…„„……††„‚‚‚‚‚‚‚ƒ„……‚€€€€€€€~|ƒ‚€~~€ƒ‚€€‚ƒƒ~~~~}}~|~}~}}€~}~|}|~{z€‚…†††‡†……†…ƒ„………†……„††…††„†‰Š‡‡‡‡‡…ƒƒ„„„………„„„ƒƒ„„……„„„ƒ…††…„„…†‡†…„„„……„ƒ‚ƒ…‡†‚‚„„ƒƒ„ƒ„ƒ€€~€€€~{{~€€€~€€€€‚ƒ‚‚}}|{|}~|~}€}|{|}}{}}}~}~}|x~‚ƒ‚„„…†……„ƒƒ„„„„„ƒ‚‚ƒƒ†Œ‰‚€ƒ„ƒ…‰‰†„„„…„„ƒ„‰ˆ†„„…„…†…„…†††……†††…ƒƒˆ‡‡…€€€€ƒ„†…„„……„ƒƒ‚‚€€‚€€€~~~}z|~}}~€€‚€€‚ƒƒ‚~|z|~€~||||}|||{|}|||~~||}‚ƒ„„„„„„ƒƒƒ„………„ƒƒ„„„‰‹„€~€€ƒ‚„‹†„„„„…„‡‹Œ†„…„„……ƒ‰Œ††‡…„„„ƒ‚†‚‚…€„€‚€„†„„…„ƒ„ƒ‚€€€‚€€€~{{~}~~|€€€‚€~{|€€}|~}~}|~||~~}}}~}~}~€‚‚ƒ„„ƒ„„ƒƒƒƒ„……„…†…††ŠŒƒƒƒ‚‚††ˆ†„……„ƒ‡Šƒ‚ˆ‡„†††…†„Šˆ‡„…††…†„ƒƒƒ~ƒ†‚ƒƒ‚€‡‡„„…„ƒƒƒ‚€€€€€€‚‚~z~|€~~~€€€‚‚ƒ…ƒƒ}}||z~}||{z~}}||}||~~~}~|}‚‚ƒ‚ƒ„„„ƒ‚‚ƒ„…………††…†‡‡Œƒ}€…‡………ƒƒ…‚„„……„„І„‰‚………†„ƒ‡ƒ…ƒ„…………ƒƒ†‚ƒ€€~††‚‚ƒƒ„ƒƒ‚€€€€€€€|}ƒ€{}}~€‚€€ƒ‚€€„…}~{{}z}€~}€}|~|}~{}~~|}~}|~ƒ…‚ƒ……ƒƒ„ƒƒ„…†††††…„…ˆ‰…†††………ƒ€‚………‡…††€€‰‡…„„…„ƒ…‚‡„†‡†……„‚‚€……‚‚„€€††„ƒ‚„„ƒ‚€€€€€‚‚{{‚€}€€€‚ƒ‚€€ƒ‚z}}}~|||}~~|}}|||{{}€~~~}}‚ƒ‚„„„ƒ„„……„……………†††…‰†€€ƒ…††‡……††……………††Š„ƒ‚ƒ‡‰…„„…„„…†‰ƒ†„„„„„„€€†ˆ‚„ƒ}ˆ†„„„ƒ„„„„‚€€€€‚ƒƒƒ„ƒy{€}€€~~}~€‚‚„……„‚~~~|}}{|~}~~~~}|{}}}€~||~€‚„„„„ƒ…„ƒƒ„„…†‡……††ˆ‡€‚„………………†‡†…†††‰‡…†€ƒ…ƒˆ„„†…ƒ„„†‰ƒ„††…„ƒ‚ƒƒ€„ˆ„…‡‚~ƒˆ…„ƒƒƒƒƒ„„‚‚‚‚ƒƒ…ƒ|z~}}}}~~~~~~~~„‡‡†„€€~~{|}~}||}~€~|}~}~€€€}}~ƒƒƒƒ„„„…„ƒ„„…†………†‡‡†ˆ„„‚‚„…††…†………………Ї€ƒ‚ƒ‚…‚‡ˆ†…ƒ††„‰…†……„„ƒƒƒƒ~€ƒ…Š€ƒŠ†ƒ‚‚ƒ„„ƒ€‚ƒƒ‚ƒ‚|€~}€~~~~~‚‚‚‚ƒ„~~~~}}{~}|}~~}{~}|}~~~~~~z|„ƒ‚‚ƒƒ………„ƒƒƒ„††‡ˆˆ‡„††…ˆ„…†††………‚„…„ˆ‹ƒ‚‚ƒƒ„…‡Š„„ƒ‚†ƒ‚…ƒ…†…ƒƒ‚„„‡‚€~†‡~€‚‹…‚‚ƒ‚‚‚€‚‚‚ƒƒƒ‚€}}~~€€~}~~~€€‚„~||}~~~~€~|}~}~€~}}~~}x|ƒ‚‚„„ƒ……„„ƒ‚ƒƒ„…†††‡†„‰†ˆˆ„‚ƒ„„‡‰„…†‰ƒ€ƒ††‡‡†ƒ‚†„ƒ„„‡…†…„„„ƒ‚ƒƒ„‰†€‚„Œƒ‚‹ƒ‚„„„„‚‚‚€‚ƒ„„ƒ„…„~|}~~}~~€‚ƒ~~€‚ƒƒ‚ƒ|{|~}}~~€€}~~|}~~~}}{|{~y|ƒ‚ƒ„„„„„„ƒ‚ƒ„„„„„†‡†…†ˆ„„ˆˆˆˆˆ‡†Š†„‹†ƒ„††‡‡†‚„ˆƒƒ‚…†…ƒ…†„ƒƒ‚†ˆ‹‚‚ƒ‰‡‚€‚„ƒ„„ƒ„ƒ‚‚‚ƒƒ„ƒƒ„…|}~}~}~‚„~€‚‚‚€‚†…z{|}}|{}~|~}}~}}}~}||~~~~~z~‚ƒƒ„ƒ„………„„„„……„…†‡‡†…„„ƒ†ˆ‡…„…††Š‚ƒƒ‚……„„ƒ€ƒŠ„„‚†„ƒ‚ƒ…ƒ‚„ƒ€†‡Œ‚ƒ€„Šƒ€Œ…ƒ‚ƒƒ„ƒƒ‚‚ƒƒƒ‚‚ƒ‚ƒ„ƒ|~„€~€€~~|~€ƒ~€€€€€€€‚‚ƒ‚|z|~~}}~~}~|~€€~|||}}~}|~€€y~ƒ€ƒƒƒ‚ƒ„…„„………………†††…‡†ƒ„„„‚ƒ„…„‚…Œ‡‚‚‚ƒ‚‚‚…†‡‡„‚†‚ƒ†‡‡…„Šˆ€…„‹†„ƒƒƒ€ƒŠˆ‚ƒƒ„„ƒƒƒƒ„ƒ‚‚‚ƒƒ„„y€€~€€~}|~}€ƒ~~€€‚‚}|{|}}|||}|||~}}~~||}~~~~~zƒ…ƒ„ƒ‚ƒ„„„„„„†……„…………„……†„ƒ„ƒ‚ƒƒ‚„…ˆ„‚‚ƒ……„ƒ„…ƒ‡‡„…ƒ‚ƒƒ‚ƒƒ‚ˆŠ€…ˆ‹…„„ƒƒƒ„~„‹ƒƒ‚„…ƒƒƒƒƒ„ƒƒƒƒ„…ƒ~y}~~~}||}€}|}~}€‚ƒ€|{{}~}||{{|}}}~~}~€~~}~}~~~zzƒ…ƒ‚‚ƒƒƒ„„„„„……„„„„……„……††…††„„……†…ƒ‚‚ƒ„…„„ƒ„…„…„ƒ†„„„„‚ƒ„„ƒƒˆˆ‚†ˆ‡ƒƒ„…„ƒ„„‚…ˆ…‚ƒƒ„ƒ„„„…„…„„„„„ƒ}x~}~~}~€~}~}}~}z{€~|zx€€|{~|~~~}~~}}~~}~~~}}zx‚…‚ƒƒ‚‚‚ƒ„„ƒ„„…………„„„„………†‡‡††‡‡†…†…„ƒƒ„„ƒ„…„„†„ƒ‚ƒƒƒ‚‚‚‚ƒ„„ƒ‚ƒ„„„……ƒƒƒƒ‚‚ƒƒ„„„ƒƒ…„ƒ„…ƒƒ‚}~x|~}}~~}}€~}~€€~|z}~}|{{xuyy||€‚‚||}}}~}||~~~}€~{y‚†‚ƒ‚‚ƒƒƒ„„„„…………„„„…„ƒƒ…††††††…………ƒ„…„ƒ„„……„……„„……„ƒ„„ƒ‚‚‚„„„…„„‡†…ƒƒƒ„ƒƒ„„ƒƒ„………†…ƒƒ„„„„ƒ‚‚x}~~€}~}}~}~~€}zzyyzyyyvuuuvwux|ƒ~{}}~~~}}}~~~}}{y‚†‚‚‚ƒƒƒ‚‚ƒ„……„…††…………………„„……†‡‡†……„„ƒƒƒƒ……ƒ„…„……„ƒ„„„„„„„……………„……„„…ƒ„………††…†‡‡†‡†…………„ƒ‚ƒx}~}~~}}}~~}~}||{{zyyyxwwvuwxzyy{€~}|}~|{||}||||}}~||€~yx‚…‚€€€ƒ‚ƒ„„……ƒ„…„„…†…………………†…………††‡†††„ƒƒƒ‚‚ƒ„„…†††„ƒƒ„……………††††……„„………†„„……………„†‡†…„…†……„„„ƒ‚~ƒ…w~€€~}}}~}}||{yyzywwwvuuxxz{x{‚~}|{}{|}~~}}}}~~€}~€{x……€€‚„………„……„…†……………„……„††……„„…††…ƒ„„‚ƒ‚‚ƒ„……ƒƒƒ„ƒ‚„„„ƒƒ…†…„„………††…„„„…†………„„…………………††„ƒƒ}~ƒƒv}€~~}}}~~~|z|yxyywxxuuvxxyxx{€}|}|}€}~~}}zw‚††‚‚€ƒ„„ƒ„……„††…………†…†…„……†…„„…††††……‚ŠŒƒƒ…ƒˆˆƒ‚ƒ‡†‚‡Š‹Š†‚„„…………„„„„„„†‡††…„„…†‡‡†„„„„„‚‚}}u{~€~~~~}}~}}{{{zxwvxvussux{xuy€~~}~}~}~}}}~~~~~}}}ywƒ…„‚‚€„„„ƒƒ………………………†„„…………††…ƒ„……„…………Љ‡‚„Š€ŒŒŒ„…††……………„„………††……†…„„„…„„„„ƒ~~€‚w|~~€€~€~~}~€~|{xxwvwwusrwwxvvz€€~}|}~|}}}~}~}}|{{}wxƒ……ƒ‚‚„‚€€‚„„„„„„„„„„…†††††…‡†††„„…†„ƒ„„„‡‰„‰ƒ‚‡‰Œ‡‹‰‹‚ŽŽƒ„…ƒƒ„………„…†…††……„„ƒ‚‚ƒƒƒ„„ƒ‚€ƒ„‚}z~~~~~€~~~}~€~€|||yxwwwwuspyxvtuz€€~~{}€~}}{|}|~}||}wzƒƒ†„‚ƒ„ƒ„…„„„ƒ……………††…††††‡†…„…………„„„„…ІƒŠ†€‡‰…Žˆƒ‹†‹‚‚€‡„…„………………†‡††…„„ƒƒƒƒ„……ƒ‚ƒ„‚€„ƒy|~}~~€}}€~~~~z{|zxxwwvsozyxvvz‚|z||}€~~€~||~~~w{ƒ…‡ƒƒƒ‚…†‚„„„……†…„………†…„„„„…‡………††…„ƒƒ‚†‰„ƒ‡ˆ„†ˆ†‹†ˆ‚„Š‚€…އ„„„„„„„„…†…„„„„„ƒ‚ƒ„†††„ƒ‚€€€‚‚‚{{~}}}€€~}}€{|~zyzzvsrryxxyyz€}||{}~}|}~{|~~}}x{‚…‡„ƒ‚‚ƒ„ƒƒ‚‚ƒ„…†………†††…„„„„„††…„……††††…Šˆ††‡‡‡‚†‰„‚ˆ‡ˆ„„Œˆ†‹Ž‰„„†…„ƒƒ„„„„…„…†…††…ƒ…†‡††„„~…ƒ‚‚ƒ{{~~~}}~~€~~~}zzz{ywvtutxxwwvz~z|}~|}}{|}}|}‚€‚€y}‚ƒ…„„‚‚ƒ‚‚ƒ€ƒ„„„„„……†††…„„„„†‡‡…†…‡††‡ˆˆ……ƒƒ†…„‡‡„ƒ„†…„‰‰‹Žˆ‚„ˆˆ††……††…„„„…„ƒ„…„„…‡†„ƒ„ƒ€€‚„…„‡}|~~}€~}}~~}~}|zxxxvvusyxwwwz}€||}~~~|}}|{|~‚}w}ƒ‚…‡†…„„ƒƒ‚€‚„„ƒƒ„„†‡††††………†††…………………†……††……„„„…„††„ƒƒƒ‚ƒ……„„†‡†‡‡‡‡††…„„ƒƒ„……………†……„ƒ€€ƒ…ƒ„{|~}€€}~}~~~~}zzyzzxvvtxxxwwy|~~~~~~}~}|~}}||}~~~w|ƒ„†‡……††……ƒ„ƒƒ…„„„…††…††…„„„……††††‡‡†……†‡ˆˆ‡‡‡‡††††ˆ‡††…„…†…†‡ˆ‡‡ˆ‡‡‡‡††‡†…ƒƒ„…†‡‡†…………ƒ€‚ƒƒƒ„‚€{|€~~~€€~~}~€{y{yyxwvvvwxxuuv~€€~}}~||}|}}€~~€€}w}„†……ƒ„†„„ƒ„„‚„…„„„……„…………„„…†††‡ˆˆ‡‡‡†‡‡ˆˆ‡ˆˆ‡‡†ˆˆŠ‰‡†††‡‡†‡‡ˆˆ‡†††††……†…ƒƒƒƒ……†‡††……„‚€€…ƒ†y}€~~}€€~~~€~|zxxxxxwvxvuussz~}}~~~~~|}~€€}}}|u}„„„„ƒƒ„†…„ƒƒƒ‚ƒ„……„„……„††††…„…†…†‡‡†„„„ƒƒ‚ƒ„„„……†††‡‡‡††‡………„…†‡†……††††…„„„ƒ„…‡†††……††…‚ƒ…ƒƒ……„€ƒƒxz~~}}|~~€{~}zyxxxvsuttwus{}~~}~~}|~~|}}|~€|w„…„…ƒ„„„ƒƒ…„ƒƒ‚‚ƒ„…††††‡†‡‡†‡‡†……†„ƒƒ„„ƒƒ‚ƒ‚ƒ…†…„…‡…„ƒƒƒ‚‚‚‚„……†…„„…†„……†††††††††…ƒ€ƒ†„„†…‚ƒ…zx}}€€}~~}~~~}€‚}{|{zywvurvutvwxy€~}|~~~€~}~}~|~}w……ƒ‚„„……ƒƒ„…ƒ‚‚ƒ‚ƒ„……†††‡‡††††…„„………ŠˆŠŠ‰‰‰ŠŠ†‚„†…„„†…ƒ‰‹Šˆ†ˆŠ†‚ƒ„„„ƒƒ‚…‡††††…†…††…ƒ€…„ƒ‚„…ƒ‚€…ƒzz|~~~~€}}~~€ƒ}x{|zyywttvuvrswz~~}||||~€€}~~~~~w~„ƒƒƒ„„†…„†…„ƒ‚ƒ…†…ƒ„………††††††††††………‡…„‡‡‰‰‡†‡‹„‚„ƒ…„ƒ‰Œˆ†…††…†‰‹…‚„„„…„„††‡†…„……………„‚ƒ„„„„………‚‡‚z{}}€~}}}~}}€~‚}{z}|zyyvurqtvttww|}~~~|}~}~~}~~€~x„„„……ƒ„…„…„„‚‚…‡†…„ƒ„„……„…†‡‡††††…††ˆ„ƒƒ„„…ˆŒ……ˆ‚ƒƒƒ‚ˆŠ†‡‰‰Š‹Œ‰…‡Œ†ƒƒ„††††…†…„…††„„„‚ƒ€‚ƒ„†…„…€y{€}€~|~}}~~€‚€}{zxvxuuuwvvvv||~~~~~~~€€~~~}w……„…‡…‚„††…„ƒƒ„…†…†„‚„………………†……††††‡ˆ…ƒ€‚ƒ‚†ˆ„‡ˆƒ…ƒ…†‰Šƒƒ„ƒ†‰„‡‹‡†……„…‡†…„…†††„…ƒ€‚ƒƒ‚ƒ…†„€„†€y{~~~~€~|€€€~||zyywtwyyxwxz~~~€~~€€€€}}~~~}v€„ƒƒƒ……ƒƒ„…††††„‚‚ƒ…„ƒ„„…†……†…„††††‡‡„†€ƒ…†…„‡Š†Š„„…‡ˆ‰‚ƒ…†……ƒƒ…„ˆ‰††„ƒ…‡†„„…††……„‚‚ƒ„ƒƒ„„ƒ‚……y|}~~~~~€€}}~€~}}~~{zxzzvqvxyxxwuz~}~~~~}~~~~~z€‚‚ƒ„„„„ƒ„…††…„ƒ‚ƒƒ„„„„„………‡‡……†………ˆŠ†ˆƒ†‡††…„‰…‡ƒˆ†ƒŠ„ƒ†……„„…ƒ„ƒ…ˆ………………†„„…………„‚€‚‚„„ƒ‚ˆƒy{|}}}~}}~~}~}}}{|zyyxvyyxyzywz|}~~~~~~~~~}~~€z~ƒ„ƒ„…ƒ‚ƒ„†„„‚‚ƒ„„…†††„ƒ„„…†……††††…‡‹†ˆƒ…†………ƒ‡ƒ††€‰„†Š…………„„„ƒƒ„„…Š„…‡†††‡†…†…„„‚€ƒ„ƒ‚‚„„„„…„†‚€x{~~}~€€~~}}}}{y|yx{zyyyz{{}~}~~~~€}}}~€€z€‚‚ƒƒ„†„„„„……†………†……„„††„‚„…†††††„‡‹†ˆ‚……„„„ƒ…€…ˆ‚‡„ˆ‡…‡‡……†…ƒƒƒ†‹„………‡†‡†‡…ƒƒ‚€ƒ……„„…†…‡…ƒ††ƒ~v}~|}}~}~~~€}{}}~}|zx|{xyyz|zxxy}~}~}}~}~}{~~~~}{€‚‚‚ƒƒ††„„„„…†‡†………„ƒ………ƒ„„……††††„‡‡†‡‚„„„ƒƒƒ‡‚†ˆƒ‡„‡…‚‡†……†…„„€„ˆ‰…††‡‡‡‡††…„ƒƒ„„‚ƒ‚„……„„„‚‡†„{~~~~|~~~}}~~€|}}}}|z|zywwwyzxxz~}}}~~~~~}€z|€ƒ„„ƒ†„‚‚ƒ…††„„†……„††ƒ„†…ƒ„„„……††…‰ˆ…ˆ‚„„„„ƒƒ†‡…†‡†ˆ‚…††………†‚ƒ†‹ˆ…††‡‡‡†…„„„‚‚„„‚„ƒ„…„…ƒ€…„„y€€€€~}}~~}‚|}}~}~€~{}uuwyxxx{}|}}}|}~}|€~~~~~~~{ƒƒ…„„„……„„ƒƒ„„„…†‡†††…††‡†……ƒƒ„ƒ„…†…‹‰†ˆ‚„…„„„‡…„ˆ‚…‰†Šƒ‚…………„‚††Ž…„†††…†……„ƒƒ‚‚„…„ƒƒ„„„…‡…‚€„††ƒz€€~~~~}}~}~€‚~}}}}}}}~}}xvxyyzyx{|~|€€~~~}€~|~}{ƒ„ƒƒƒƒ„…†…„ƒ„„„„„…………†……‡‡‡‡‡…ƒ„„………ƒ‹††‚€ƒƒƒƒ‡‡ˆ‰ƒƒ„‰††‡‚ƒ„„„€€…‡‹Šƒ…†……………„‚ƒ‚‚ƒ………ƒ„…„„…†„ƒ‚‡‡†ƒ{}€~}~~~||~€€~~~|zy|~{yxxz{zz|}|}}~~~~}~€€}|}z}…ƒ‚‚‚ƒƒ„…†„‚ƒƒƒƒ…………†‡†…‡‡‡‡……†……………ƒŠ„ƒƒ„‚ƒ†‡„…Š……†…ˆŠ‚‡‡„„ƒƒƒ††‹ƒ„†‡‡‡†…„ƒ‚ƒƒƒ„††……„††††…ƒ…ˆ†…ƒ€~~}€€‚€}|~€€€~~}{yyz{xy}€|{zxyyy}~|~€~~}}}||}}~z~…ƒ‚‚‚ƒƒ„††…ƒ‚„…………††…†††„ƒ‚…‡‡…………ƒ‰…„…‡†…†††‹‹„…†††…ˆˆ„…‡†………†Š‡„†‡‡‡†‡„‚‚ƒ…„„……††……………„ƒ†„„‚€}~~€€€~||}€€~||{zy{}zz{yzzxvyxx{}|~|}~}~}}}}zz…„ƒƒ‚‚‚ƒ…††…ƒ‚ƒƒ……„„„……†…„……†‡†„„††„Љˆ‡‡‡‡‡ˆŠˆ…„†‡‡†…ƒ‡Š‡„ƒƒ„…Žˆ†‡‡‡††……„‚ƒ‚„„ƒƒ‚ƒ†…††……†ƒ‚…„„‚€‚€€~}€}~~~}~}~~~}||||}|{}|{yx}}yz||}}~~~~|}{{ƒ‡‡ƒ‚‚ƒ„„††„ƒƒƒƒ„……„ƒ„………†ˆ‰‡ˆ‡…„†‡††‡‡†‡ˆˆ‡‡†……‡‡‡‡‡††………ˆŠŠ‹Œ‹Š‡†‡‡†††…„ƒƒ‚„„‚ƒ„……„………†‡†…„†…„„„‚ƒ}}|~~~~‚„‚€~||~{z€{|{{yz|}zy~}~€~~€~~€y|„…†ƒƒƒƒ„„†……„‚‚ƒƒ‚‚‚„…†‰‰‰‰‡‡††‡†††††‡‡…ƒ„„…†‡†………††††…„„………ƒ„†††††††„ƒƒ‚€‚…†††„„…………„€„ˆ…„ƒ‚„„}~~€‚}}€‚ƒ€€~|}||||{{{~|{{zyyzzzz~}~~}|~|~~||}y~‚„…‚‚„ƒƒ„„„…††…‚‚‚‚…‡‚„………‡‡ˆˆ‡ˆˆˆ‡††‡‰Šˆ‡‡††‡‡†…„…††……†††…††††‡†††…„„ƒ‚~}€ƒ…ƒƒ……„………‚†‡†„ƒ‚„ƒ~‚~~~}~€€~~€~||}}zy{||{{|yzxxz{yz|~~}}~}~~}~~}}€|z€€…†‚‚ƒ„„„„…††………ƒƒ‚‚ƒ‰††…„„„ƒ‚ƒ‡‡ˆˆˆ‡‡‡ˆˆ‡‡†††………†…††‡†††‡†††ˆ‡††††„‚‚‚~~‚‚ƒ…„„„ƒ…†…„„„……†……ƒ…„ƒ€}~~~}~~}~€~}}~||}|{z|||}}|}~{}}~{}~}€}~€~z|„ƒ„†‚‚ƒ„……„†‡‡†‡…ƒ‚„ƒ„ˆ€€‚ƒ„‚„‰††„‚…†‡†††‡‡††…„……………†††‡‡‡††‡††…„ƒ„……„‚‚ƒ|…………„Œ‰……‡‡…„„„„‚†††…„„„‚€}€~}~~~~~~€~~~}|z|}}z||~~~}{|}}€~~~}~‚}}x}‚„„†‚‚ƒƒ„„ƒ„‡‡†‡„‚‚ƒ†‡~}‚ƒ„‹‡†ˆ…‚…†‡‡†…ƒƒ‚‚††…†‡††‡‡††‡‡…ƒ€~€‚‚„„€ƒˆ‰ƒ~†ˆˆ‡‰‹Ž…ƒ…††…„„ƒ…‡…„…„‚ƒ‚€|€~}}~~~}}~~~~}}}}~~~|{}}}~€}}}€~~~~~}}{€…ƒ‚„‚„„„„„…†…†‡…‚ƒ‚ƒˆ…~~|ƒƒŠˆƒ…„„…†††‡„‚‡‡‡„„ƒ„††††††…ƒ€‚„„ˆ‡‡‰‹‰„‚€…††‰……††‰ˆ„„ƒ……„„…‚~‚††…ƒ‚‚‚……ƒƒ{€}~€‚€}|€~~€||}}~}}€}~|}|~~}~}~}{z„ƒ††‚‚ƒ„ƒƒƒ„†††††………„…ƒ‚‚~‡Šƒƒ†‚„…††††ƒŠ††ƒ‚…†‡‡††‡„€…†‘‘ŒŠ„Š‹€…{‰†…†…„„ƒ„„…††…„„‚‚„††…„ƒ‚…„‚€€}~€~}~}}}~~~}}}}|||}}|||~~~}~}||~~}€}|~xz‚ƒ„††ƒƒƒƒ„„„„†‡†††‡†„„†ƒ‡‚€„‰„‚…ƒ………„„ŠŠƒ‚„†‚‡‡‡†‡†‚ˆ‹‹ˆ†……Љ€‚†|†‰‡‡††…„ƒƒ„†…„……„‚ƒ…††…„„‚ƒ„‚€€‚~|}}~~€€€€||~{|}|{|}€~~~{}~}|~}€~€~}~|‚ƒƒ„…ƒ‚ƒƒ„„ƒƒ„…†††‡…„ƒ……}†„}ƒ†„…€ƒƒ…†…‚ˆ‰‡ŒŠ„ˆ…†††‡„‚ŠŒŒ„‚„„„„„ŠŠ‚‚†‡€‰†…†……„ƒƒ„………††…ƒƒ…‡…„ƒ„„ƒ††„‚ƒ~€~~€€€€~~}{}~~~~€~|~~}€€}}~~~}||}‚„„„„†ƒ‚ƒƒ„ƒƒ„„…†††‡††‡‡††‚ƒ„…„ƒƒ„†ƒ†‹…І‹†††„††‡†ƒ‹‰‰ƒ„„…†…„ƒƒƒƒˆ‡€Š‡†…ƒƒƒƒƒ„„……………ƒ…‡†„ƒƒ„ƒ„…„„„ƒ‚~~€~€€~{~~€€~~|€€~~~€‚€€~~|}~~~{|z}„„ƒƒ…ƒ‚ƒƒ„„…„…†„„„„…†‡‡ˆ€|‡…€„„‰ƒ€„„„„ƒ‰…ˆ…€ˆˆ‚‰ƒ„††„†‡†Š‚„……†„ƒ„‚€‚‚„‰„|‰‰……‚ƒ…„ƒ†‡†……„ƒ‚„††„„……‚€„…ƒ‚ƒƒ‚}‚€~~}€|€€€~}~~~~€‚€}}ƒ~€~€~}~}zz€„…„„††„„ƒ„„„ƒ„…„„ƒƒ„‡‡ˆ‹„~…‡„…ˆ‹ƒ„„ƒ‚ƒˆ†ƒŠ…ˆˆ…ƒ„‚‡…„ˆ‚„…„ƒƒ€€ƒ‚ƒ‡…~ƒˆ‡‚„‚ƒ…‚‚ƒƒ„„…‚„‡†„„„„‚€„„‚ƒƒ„ƒ~‚ƒ~‚€€€€€€€||}}|~‚€~~}}}~~~~~}}€‚ƒƒƒ„…ƒƒ„„„„ƒƒ…††„„…††‡‰„…‡‡†ƒ…‡‚ƒ„†„…‡ƒƒƒ€‚†„…†‚ƒ†ƒ…†ƒƒ„„ƒ…ˆ…„‚ƒˆ‡Š€„…ˆ€€ƒ„‚|‰Œ…„…„€ƒ„…†„…†„€„ƒ„…ƒƒ‚‚~~€€€€~~~~}|{|}‚ƒ€€€€€„…€}~~}}~|}‚ƒƒƒ„„…ƒ‚ƒ„„…†…††……†……„„ˆ‡‚ƒ‡Œ‰‚…ˆ‚„†„†…€€|~€€…‡‚ƒ‚‰„ƒ†‚ƒ††ƒ…‰†ƒ€ˆ€€†‰‚†ˆ†‰€ƒ’Œ„„ƒ‚„………„†‡„€„†„„„ƒ‚€~~‚€}}|{|}~||{{€ƒ‚€ƒ‚†|~~~}|~€ƒƒƒ„„„†„‚„„„………………†††…ƒƒŠ‹ƒ„‡‰ˆ†‡‰‚€ƒ„ƒ…„€€‚ƒ„‚…„ˆ‡€…ƒ††„†‹††„Š€‡Š„‚‰ˆ††“ƒ……‚„…††……‡†ƒƒ†‡††…„„ƒ€~‚ƒ~}|||~€€~~~~~‚‚€‚‚…~~~€~|€‚€„ƒƒ„„ƒƒ……ƒ‚‚ƒ…………†††……„ƒ‡Š„…‡………‡‰„ƒ…†ˆ†‚ˆ‡‡ˆ‡†……ƒ„‚„…†‰‚ƒƒ…††‡‡„‡‡Š…‚ˆ‰‚‚ƒ‹…‚€‚”…ƒ…ƒ‚ƒ†††………†„‚ƒ††„„„‚ƒ„ƒ€€€~€~~}~€€€}|}~}€ƒ‚€€~…ƒ}}|}}‚€„„ƒƒƒ„ƒ„…‚‚‚ƒ„„………††„„„„…„ƒ…………ƒ†‰„‚ƒ„†ƒ†ˆ‡Š‹Š‰ŠŠˆƒ‚‚††‰‹ƒƒƒ‚€€€ƒƒ†‰…„ˆˆƒ…ƒŒ‹ˆ‰Ž†…‡…‚„†……………††‚€‚‡†„…‡…‚‚ƒ‚€€‚€~~~~€~}}~{|~‚€€€€„ƒ{|~‚€‚ƒƒ„‚ƒ„„ƒ†‡ƒ‚ƒƒƒƒ…††††…„……††††…†‡…†Œ‰‡…‰ŠƒŠ†††‡‡†……‡†„‡‡Œ‹ƒƒƒ}~€ƒ‰„„†‹…„ƒ‚„ˆŠ‹‰„„†…‚„……‡†…………‚ƒ„…„………ƒ‚‚…ƒ€~~|~€€}~~{{~~€~}€€ƒ‚€€‚€…}{}ƒ……ƒ‚„ƒ„„…‡„‚‚ƒ„„……†‡…„………†‡††…………†ˆ‰ˆ†ŠŽŽŒˆ††‡‡‡‡‡†Š‹ŠŠ„†‡‡‰Œ…ƒ€‚ƒ‹€„ƒƒ……ƒ‚„††…†…‚ƒ…‡‡††……‚……†††…„„ƒ‚‚€ƒ…ƒƒ‚~€ƒ€€€€€}}€~}|~‚‚€€‚~}~}€…„‚‚‚ƒ……‡‡„‚‚ƒ„……„„…‡††……„„†„„…„„…†††……„…†‡‡‡‡‡†…†††‡ŠŒ‡†††…†Œ‹††‡‰‹ˆƒ‚ƒ†‹‡ƒ„„…„…‡ˆ†…‡„‚…„…………†††‚††…„„„ƒƒƒƒ„ƒ€‚‚‚~€€~‚ƒ~}}~‚€€ƒ€~‚„ƒ‚……‚‚„„…‡†„‚ƒ„ƒ„ƒƒ„…†‡‡……†‡…„…„„…†††††††………†…„„†‡‡‡††††††††‡††ˆ‹‹Œ‹‰„‚…†…ƒ‚ƒ„………†……††…€……†……„„…†„‚ƒ†‡†…„ƒƒƒƒ„†ƒ‚‚‚‚€~||}~~}~|„ƒ‚€€~ƒ‚‚~|€„…ƒƒƒƒ„…„„‡‡†ƒ‚ƒƒ‚‚‚ƒ…††……………„„„„…††……†‡†…„„ƒ„„…†‡††‡‡‡ˆˆ‡‡†††††‡†…ƒƒƒƒ„…„„ƒ……„„…†‡………„€„…†‡†„‚ƒ…†ƒ‚„††……„„ƒ‚‚ƒ„ƒƒ‚‚ƒ|{|‚~zz}{|}€€ƒ‚€€€|~€„€~~…„ƒƒ„„„ƒƒˆˆ†ƒ€ƒ…ƒ‚‚„…†…„ƒ„……„„…†…„„„………„„ƒ„†‡‡‡‡††ˆ‡†††……„ƒƒ„„…†…ƒƒ„„…†…„ƒ…††…†‡†††…‚€ƒ„…†…„………„ƒ‚…‡†„„„„ƒ‚‚ƒ…„‚„€{{|~€€~||€|{z€„€}€‚‚€€€„€€„†„„‚‚ƒ„„ƒƒ†‡ˆ‡„ƒ‚ƒ……ƒ„„…………„„„„ƒƒ„…„……†††„„……„ƒ…‡‡††††‡†„„„…„ƒ‚ƒƒƒ‚‚‚„………„„„„†‡†††††††…ƒƒ„ƒ…‡†…†‡‡…„ƒ‚…†„‚ƒƒƒ„„…„…†„ƒ€ƒ„€€}{~~}|~~}}~{€€‚€‚€~€€€…ƒ€‚…‡…ƒ„„ƒ‚…†‡††…‚„…„„„…………††††„ƒ„„„„„„„……„„…………††††††‡†……„„…………„ƒƒƒ…††……„„†‡‡††††††‡†€„„„†‡†…†††…„€……ƒ„…ƒ„††…………„ƒ…€~€}~~{z~}{{~~ƒ€~€€€~~~†ƒ€ƒ„„‚‚ƒ„…„ƒ‚ƒ…‡†‡†‚ƒ„ƒ‚ƒƒ„„…‡‡‡‡‡†‡†…………†…„„„…‡†‡‡‡‡‡†………†„‚„„‚‚ƒƒƒƒ…‡†……†……‡††…‡‡‡††„‚‚ƒ„…‡‡…„††…‡†ƒƒ‰‡„„…„ƒ„…„„†‡…ƒ‚€„€€€~~~~|~‚€€ŒŒŒ‹‹‰‰‰ˆ‰‰‡‡‡ˆ‰ˆ‡ˆˆˆ‡‡ˆ‰‰Š‰‡‡ˆ‡ˆ‰ŠŠ‰‰‰‰ˆ‡ˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡‡†††††††††††…†…„ƒƒ‡‰†††ˆ‰‰ˆ‡‡†‚‚„††……………†………†††‡‡……‡‡‡†‡‡‡‡‡‡‡ˆˆˆˆ‡ˆˆ‡‡ˆ‰Š‹Šˆˆˆˆ‹Šˆˆˆ‡Š‰‡‡ˆ‰‹ˆ‡‰Š‰‹‰ŽŒŒŒŒŒŠ‰‰ˆ‰Š‰ˆˆ‡‡‡‡‡‰‰ˆ‡‰‰‰ˆˆ‰‰‰‰ˆ‰ŠˆˆŠŠ‰‰‰‰‡‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡††………†……†††…………ƒ‡Š‰‰ŠŠ‰‰‰Š‰‡Š…€„………„„„…†††…††‡‡†…††††††‡‡‡‡‡‡‡‡‡‡‡‡‡‰‰ˆ‰‰ˆˆ‡‡ˆˆ‰ˆ‡ˆ‡ˆ‰‰‡ˆ‰ŠŠ‡‡ˆˆ‰‹ˆ‰ŽŽŒŒ‹‹‹‹ŠˆˆŠ‹ŠŠŠˆˆˆ‡ˆ‡‡‡‰‰Šˆ‡‰ŠŠ‰‰‰‰ˆˆ‰‰‰ˆˆˆˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡…………†††………††…„‚…†………„„„„„„‚„„€ƒƒ„„„„„……†††††……†††††‡‡‡‡‡‡‡‡‡‡‡‡ˆ‡‡ˆ‰ˆ‰‰‰‰ˆ‡ˆ‰‰ˆ‡ˆ‡‰‰‡†ˆŠ‹Š‡‡‡‰Š‰ˆ‰Œ‹Œ‹ŠŠŠ‰ˆ‡ˆŠ‹Šˆ‡ˆ‹Šˆ‡‡‡ˆˆˆˆˆˆ‰‰‰‰Š‰ˆ‰‰‰ˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡††††………††††…††…„„ƒ„†††…„„„…„†…ƒƒƒ‡‚‚„…„…††……………„…†‡††‡†††‡‡†††‡‡‡‡‡†‡‡ˆ‰ŠŠŠ‰ˆ‡ˆ‰ˆˆ‰‰ˆ‰ˆ‡ˆ‰ŠŠ‰ˆˆ‡ˆŠ‰ˆˆŠŠŠ‹‹ŠŠ‰ˆ‰‰ˆˆ‰ˆ‡‡‡‰‰‡†‡‡ˆˆ‡‡ˆˆˆ‰‰ˆ‰‰ˆˆ‡‡‡‡ˆ‡‡†‡‡‡‡‡†††‡††††††††……†††…†…„„……„………„„„„„†ˆ‡†ƒ„‹‡‚‚‚‚„…†††††………††…††…†‡‡‡‡‡‡†‡‡†‡‡ˆˆ‰Šˆ‡‡ˆˆˆˆˆˆ‰‰Šˆ‰ˆ‡ˆ‰‰Šˆ‡ˆ‡ˆŠ‰ˆ‰ˆ‰‰‰Š‹ŠŠ‰‰‰ˆ‡‡‡‡‡‡ˆ‰ˆ‡‡‡ˆ‡†‡ˆˆˆ‰Š‰ˆˆˆˆ‡‡ˆ‰ˆˆ‡†‡‡‡‡‡†…†††…††…††‡†„„„…………„„ƒ„…„……„ƒ‚…†††„‚„†‡Š‰‚„………†††…†††‡……†††‡‡‡‡††‡‡‡‡ˆˆ‡‰ŠŠˆ‡ˆˆˆˆ‡‡‡ˆ‰Š‰ˆˆˆ‡ˆ‹‰ˆˆˆˆ‰ŠŠ‰‹‹‹Š‰‹Œ‰‰Š‰Š‰‡‡‡‡ˆˆ‡‡ˆˆ‡‡‡‡‡‡ˆˆˆ‰‰‡‡‡ˆˆ‡‡ˆˆˆˆˆˆ‡†††‡‡‡‡‡†‡††……………„„„………„„„‚‚………„ƒ€}~€‚ƒƒ…ƒ‚„ƒƒ†„ƒ„„„„………„„……………†††‡†††††††††‡ˆ‡ˆˆ‡‡‡‡ˆˆ‡‡‡‡‡ˆˆ‰‡†ˆ‰Šˆ‡‡‡ˆŠ‰ˆŠŠŒ‹‹ŠŠŠˆˆ‰‰‰‰ˆ‰ˆ‡‡ˆ‰‡‡‡‡‡ˆ‡‡‡ˆˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡ˆˆ‡††‡‡††††…†………„„„„„„„„„„„„„‚†…„„~~‚ƒƒ„†„ƒƒƒƒ‚‚……†………„„„„„……………………††††‡†…††‡ˆˆˆ‡‡†ˆˆ‡ˆ†‡‡‡‡ˆ‡‡‡†ˆ‰Š‹ˆ‡†‡‡Š‰‰‰ŠŒ‹Š‰‰‰ˆ‰Š‰‰‰ˆˆˆˆ‡‡‰ˆ††††‡ˆˆ‡‡‡‡ˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡†††………„…………„„„„„„…„„„ƒƒƒƒ„‚€†„ƒ„„€€ƒ‚‚ƒ„ƒ„ˆˆ„ƒƒ„„………………„„„„††„„…………„…†‡‡††††‡‡‡‡‡††‡ˆ‡‡ˆ‡‡†‡ˆ†‡ˆˆ‰Š‰ˆ‡‡‡ˆˆˆ‰‰‰ŽŒ‹Š‰‰‰‰‰‰‰ˆ‡‡‡‡‡††‡…………†‡‡‡‡‡‡ˆ‰‡†‡†‡‡‡‡‡‡‡‡‡†††††………………„„„„„„„ƒƒƒ„„„„„„‚‚ƒƒƒ††‚……ƒ‚‚€ƒ…‡…ƒƒƒ„„ƒƒ„„„ƒƒ„„……„„…………………†††…†‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ˆŠŠ‡†‡ˆˆˆ‡‡‡ˆ‰Š‰‹ŠŠ‰ˆˆˆˆ‡ˆ‰‡††‡‡†…†……†‡‡‡‡‡‡‡‡‡††‡‡‡‡‡‡‡†‡‡‡‡‡‡‡†…„„„ƒ„……„„„„ƒƒƒ„„„ƒƒ„ƒ‚ƒƒƒ„†„†…‚‚ƒƒ€€ƒƒƒƒ‚„„„„„ƒ„ƒƒƒ„„„„„„„…………„„„………†‡‡‡‡‡‡‡†‡‡‡‡‡†ˆˆˆ‡‡‡‡‡‡Šˆ††ˆˆˆˆˆˆˆˆŠŒŠ‹‹ŠŠŠˆ‰Šˆˆˆˆ‡‡‡ˆ‡‡‡‡ˆ‡†‡††‡‡‡‡‡ˆ‡‡‡‡‡‡‡‡‡††‡††…„………„ƒƒƒƒ„…„ƒƒƒ„„„„„ƒ‚ƒƒƒƒƒƒƒƒ…ƒ‚‚‚„‚€‚‚ƒƒƒ„ƒƒƒƒƒƒƒƒ„„ƒƒ„„„„……„„…………††‡†‡‡‡‡†††‡‡‡‡‡†‡‡‡‡‡ˆ‰ˆˆ‰‡††‡ˆ‰‰ˆˆˆ‰‰‹Œ‹Š‰‰‰Š‰ˆ‡‡‡‡ˆ‰ˆ†‡‡‡†‡‡‡‡‡‡‡‡‡‡ˆ††‡††††††‡‡†………………„„ƒƒƒƒ„„„ƒƒƒ„„ƒƒƒƒƒ‚‚‚„‚‚‚‚‚ƒƒ‚‚‚…ƒ€‚‚‚ƒ„ƒ‚ƒƒƒƒƒ„„ƒƒƒƒ„„„„„„„„………†‡‡‡‡‡‡†…†‡††‡‡‡‡‡‡‡‡‡…‡‡ˆ‹Š‡…†‡‡‰‰‰‰ˆˆˆ‹Œ‹Šˆˆ‡ˆ‡††‡‡‡‡ˆ‡††‡†…†‡‡‡‡‡‡‡‡‡‡†††‡‡‡†††††††††……†…„„ƒ„„„„ƒ‚ƒƒ‚‚‚ƒƒƒƒƒƒƒ‚‚‚„ƒƒ„ƒ„ƒ‚ƒƒƒƒ„„„ƒƒƒƒƒ„„„ƒƒƒ„………††††††‡†……†‡†‡‡††‡‡‡‡‡†††‰‰‡‡‡‡‡‡‡‡‡ˆ‡ˆ‰ŠŠ‹Šˆˆ‡‡‡ˆˆˆˆ‡‡‡††…‡‡†‡‡‡‡‡‡††‡‡‡‡††‡‡‡†††††††…††……„ƒƒ„„…„ƒ‚‚ƒƒ„ƒ‚ƒƒƒƒƒ‚€ƒ„…„‚‚‚‚‚€‚ƒƒ‚‚ƒ„ƒ‚„ƒƒ‚‚ƒƒƒ„„„ƒƒƒƒƒ„…††††…†‡‡‡‡‡‡‡‡†††‡‡‡‡‡†††ˆˆ……†‡‡ˆ‰ˆ‡‡ˆ‰Š‹ŠŠŒ‹ŠŠŠ‰‰‰ˆˆ‡ˆˆ‡‡‡‡‡‡‡‡†††……†‡‡‡‡†‡‡‡‡‡‡†††………††††…„ƒƒ„ƒ„ƒƒ‚ƒƒ‚‚‚‚‚‚ƒƒ‚‚ƒ…„‚‚‚€‚‚‚‚ƒ‚‚‚‚‚ƒƒƒƒƒ‚ƒ„ƒƒ„„„„„„„„„…†‡†††††††††………………††‡‡‡†‡ˆ‡††…†‡‡ˆˆˆ‡ˆ‰ŠŠ‰Š‹Š‹‹ŠŠ‰‰Šˆˆˆ‰ˆ‡‡‡‡†…†…†‡†…†‡‡‡‡‡†‡‡‡‡†††……………††…„„„„„„ƒ‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚~€€~~~~~€‚‚‚‚‚‚‚‚‚ƒƒ„„„„„„„ƒ„„„………††††‡……††…„„…†††……†‡‡‡††ˆ†…„…‡‡ˆˆ‰ˆ‡ˆ‰Š‹ŠŠŠŠ‹‰‰‰ŠŠŠŠ‰‰ˆˆ‡‡‡††‡‡††‡‡†…†‡‡‡‡‡‡†‡††…„„„„……†…„„„„…„ƒƒƒ‚‚‚‚‚‚‚€‚…††…‚~~€‚ƒ‚ƒ…„€‚‚‚‚ƒ„ƒ„„„„„……„…………„„……„„…††„„…††††††‡ˆ‡††ˆ†„„†‡‡‡ˆˆ‡‡ˆ‰‰‰‹Š‰Š‹ŠŠ‰Š‹Š‹Š‰‰‰ˆ‡‡†…†‡†‰ˆ‡†…†‡‡‡‡‡††††……„„„„„„ƒ„„„„„„ƒƒƒ‚‚‚‚‚‚‚‚‚€€„…†ˆˆ‡†ƒ„„…†…ƒ€€‚‚‚‚‚ƒ‚‚ƒƒƒ„„„„…………„„„…†††………†††††††‡‡‡‡††‡‡……††‡‡‡††‡‰‰‰‰ŒŒ‹Š‰‰Š‰‰Š‹‹Šˆˆˆˆ†††……†…†‡‡††‡†††………„„„„„„„„„„„„„ƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€‚„‡‡„€~}}~‚‚‚‚‚‚‚‚‚ƒƒ‚‚ƒ„ƒƒƒƒƒƒ„„„„„„………„…………†††…‡‡‡‡ˆ‡†††…†‡‡‡‡†‡‡ˆˆ‡ˆ‰‹‰ˆ‡ˆ‰‰Š‰ˆŠ‹‹Š‰‰ˆ‡†………†‡‡‡‡†††……„ƒ„„„„„„ƒ„„„„„„„…„ƒ‚‚ƒƒƒ‚‚‚‚€€€€€€€€€€€€€€‚ƒ†‰ˆ…ƒ„…‚€€‚‚‚ƒƒƒƒ„„ƒƒƒ„„„„„„„ƒ„†…†…„…††‡…†‡‡‡‡ˆˆ‡……†††‡ˆ‡‡‡ˆˆˆˆ‰ˆ‰‡‡ˆˆˆ‰‰‰‰‰‰ˆ‰ˆ‡‡‡†„ƒ‚…††‡‡…„„„……ƒƒƒƒƒ„„„……„„ƒƒ„„ƒ‚‚‚ƒ‚‚‚‚‚‚€‚‚€€€€€€€‚„†‡‚€€€€€‚‚‚ƒ‚‚ƒƒƒƒ„„„„„„„…††………†††††††ˆŠˆ†…†‡‡‡‡‡‡‡ˆˆˆˆ‰Šˆ‡†‡‰‰‰ŠŠŠ‰‰ŠŠŠ‰‡††……„‚‚…‡ˆ‡…„„„„…„„ƒƒ‚ƒƒƒ„„„ƒƒ„…„ƒ‚‚‚‚‚‚‚€€‚‚€€€€€€€€€€€€€€€€€€‚‚‚‚ƒƒƒƒ„„„……„ƒ„…………††††‡‡†‡ˆˆ†„…†‡‡‡‡‡‡‡‡ˆˆˆˆˆ‡ˆˆ‡‰ŒŠ‰Š‹ŠŠŠŠ‰‡‡ˆ‡†…„‚ƒ†‡‡†…………„„ƒƒ„ƒ‚‚ƒ‚ƒƒ„ƒƒƒƒ„ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒƒƒ‚‚ƒƒƒƒƒ„„ƒƒ„„…„„„„…††‡‡…‡ˆ†„„‡‡††‡‡‡ˆˆ‡‡‡ˆ‡†‡ˆˆˆˆ‹‹Š‹Œ‹‰‰‰ˆ‡‡‡ˆ†„ƒ‚‚†ˆ‡†„…………ƒƒ„„ƒƒƒƒ‚‚‚ƒƒ‚‚‚ƒ‚‚‚ƒ‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€€‚‚‚€‚ƒƒƒƒ„„ƒƒ„…………„„„†……††‡‡…ƒ†‡‡††‡‡‡‡‡‡‡ˆˆ‡‡‡ˆ‰‰‰‰Š‹‹Œ‹‰‰ˆ‡‡‡‡‡‡†„‚ƒ‡‡†„„„„ƒ‚‚ƒ„„ƒƒƒ‚‚‚‚‚‚ƒƒ‚‚‚ƒƒ‚|}€}}}€€€€€€€€€€€~~~€€€€€€€€‚ƒƒƒƒƒ„„„„…††…„…†††††‡ˆ„…††††††‡‡‡‡‡‡‡‡‡‡‡ˆˆˆˆ‰‹‹‹‹ŠŠ‰‰ˆ‡‡†‡‡‡‡†††ˆ‡…„ƒƒƒƒƒ‚ƒ„„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚~€ƒ‚€€€~|{|€€|{|{{|}|}}~~||}||{{z{|€||}|{||~€€‚‚ƒƒ„„„…„„„…„„„………††‡‡„……………†††††‡ˆˆˆˆ‡‡‡‰ˆˆˆ‰Š‹‰ˆ‰ˆ‡ˆˆ‡‡††††………†‡†……„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€ƒ€~}††}~~}}}|{||}€€€~€€~~€‚}~€}{}€€‚‚‚ƒƒƒƒƒ„ƒƒƒ„„„……………††…………†……†††††††‡‡ˆ‡‡ˆŠ‰‹Šˆ‰‹‰ˆ‰Š‡‡‡‡‡‡††‡†ƒ„……†…„„„„ƒƒ„„ƒ‚‚‚‚‚€€€‚}{~€ƒ‡…}€ƒ‚‚€€~‚„„„„„‚~€„€€€‚……‚€|}€€€€‚‚‚‚‚ƒ„„ƒƒ„„„…„………………†††††††…„„††…††‡ˆˆ‡‡‡‘ŒŠ‰‰‹Š‰Š‰‡‡‡†…†‡††…‚ƒˆ†„„……„„„„ƒƒƒƒƒ€€‚€€}}|~€„…‚€~………‡†‡„€€€}}‚ƒ‚ƒ„ƒ€ƒ€~}‚„‡ˆ‚€{~€€€€€‚‚‚‚ƒƒƒ„„ƒ„„……†††………„………†‡‡†…†††‡‡ˆ‰ˆ‡‡†•‘‹‰‰‰‰‰‰‡‡‡‡†„…‡††„€€€„Ї………„ƒƒƒƒƒ„ƒƒƒ€€€€€€€|~|{€…„€€€€€€€€€€€€€~}~€€€|z~€€‡…€}|€€€€‚‚‚‚‚ƒ„„ƒƒƒƒ„…†††††…†‡‡‡‡‡‡‡†…††††‡ˆ‰ˆˆ‡”’‘ŽŠ‰Š‰‰‰ˆˆ‡‡†„ƒƒ‚€€€€€…Ї…„„„„„„„„„ƒ‚‚‚€€€€€€€€€|}~~~„ƒ€€€€~~~}~~€€€~}|~~~€‚€|y~€€‚„~}€€€‚‚‚‚ƒ„ƒƒ‚ƒƒƒ„…†………†††‡‡‡††ˆ‡‡†††‡ˆ‡‡ˆˆ‡‡“ŽŠˆ‹‰ˆ‰‰‡††††„€€€€€…ˆ†††„‚ƒ„„„ƒƒ‚‚‚‚€€€€€€€€€{|~€~~~€~|}~}|{{{||~~||{{zy{|{~‚€|{~€€€~€€€€€€€‚‚ƒƒ„„„ƒƒ‚ƒƒ„…††††‡‡‡ˆˆ†††††‡‡‡‡‰Šˆ‡ˆˆˆˆ‘Œ‰‹ˆ‡ˆ‡†…†…„~€…ˆ†††…ƒ‚ƒ‚‚‚ƒ‚‚ƒƒ‚€€€€€€€€€€€~}~ƒ}~~~~~~€€€~}}|{~€~~}}}}~z~‚‚||€€€€€€‚ƒ„ƒ‚ƒ‚ƒ„„„„………†‡‡‡‡‡‡†††……‡‡‡‡ˆ‰ˆ‡‡ˆ‰‰‘’’‘ŽŠŠ‰ˆ‰ˆ‡†…‚‚€€€€€ƒˆ‡…„ƒƒƒƒ‚‚‚‚‚ƒƒ‚‚€€€€€€€€~~~ƒ€~}~~~}…†……„ƒ}}€‚‚‚‚‚€€}}||}€‚€€€€€ƒƒ‚„ƒƒƒ„„„………†‡‡ˆˆ‡‡†……††‡‡ˆˆˆ‡‡‡‡ˆ‡Š‘‘’‘ŽŒŒ‰ˆ‡‡†…ƒ€€€€€€ƒˆˆ†……ƒƒ„ƒ‚€€‚‚€€‚€€€€€€€€~}~€‚}~~~~~}€‚‚‚~~ƒ„„„„€€€€‚‚‚‚‚ƒƒƒƒƒ„„…‡†ˆˆˆ‡†„„„…‡‡‰‰ˆˆ‡‡‡‡‡ˆ‹‘‘‰‡…ƒ€€€€€€€‚ƒˆˆ†††„„ƒ‚€€€€€€€€€€€€€€~}}€~~}~~}}~~}|}~~~€‚~‚„„‚€€€€€‚‚‚‚‚‚ƒƒƒƒ…„…†‡‡ˆˆˆ‡†„……†‡‡ˆˆ‡‡‡††ˆ‰ŠŒ’‘‰„€€€€€€€€€€€†ˆ†……„„ƒ‚€‚ƒ‚‚‚‚€‚‚€€€€€€€€}~€€€~~~~||||~~€€~}}~~~~€€€‚~|‚€€€€€€€€€‚‚ƒƒ‚‚‚ƒƒƒƒ…„„…†‡‡‡ˆ‡ˆ†‡‡†…†‡‡ˆ‰‹Š‰ˆ‡††ˆŠŒŒŠ‡ƒ‚€€€€€€€€€„ˆ…ƒ„„ƒƒƒƒ‚‚‚‚ƒƒƒ‚€€€€€~~€~€~~}€~~~}{{{{||}~€~~||}~~~~~ƒ{|€€€€€€€€€‚ƒ„„…ƒ‚ƒ„……„„…„„…††‡‡†‡Šˆ†……„…‡‡‡ˆŠŠ‰ˆˆˆ‡ˆŠŒ‘‘Šˆ†‚€€€€€€€€€€€€„‰„ƒƒƒ‚ƒ‚‚‚€€‚ƒ‚‚€€~€€‚‚~~€€}~~~~~~}}}}|{z}€€~}{{||{zz}‚}|}€€€€€€€‚ƒ„„„„ƒ‚ƒ„„„…„„……††‡‡‡†ˆ‡…„ƒ„†‡‡‡ˆ‰ˆˆˆ‡‡ˆŠ‹‹‰Žˆ†…„€€~€€€€€€€€€„ˆ…„ƒƒ‚‚‚‚‚‚‚€€€~€€€€€‚€€€€€€~~€€~}z}‚~€€€€€€€‚ƒ…„‚‚‚ƒ„„………†‡‡‡‡‡†‡ˆ†…………†‡‡‡ˆˆ‡‡‡ˆˆŠŒŒŠˆ‹‰‡…„………€€€€€€€€€€€€‚‡†„ƒƒƒ‚ƒ‚€€€~}€€€~€€€€€€€‚ƒƒ‚‚‚‚€€€‚‚‚‚~}€ƒ‚€€€€€‚ƒ„„„ƒƒƒƒ„„„………†‡‡‡‡‡‡‡†„„†‡‡‡‡‡‡††††‡‡ˆŠ‰‡‡ˆ……†…„„„ƒ€€€€€€€€€€€€‚ˆ…‚‚‚‚‚‚€‚€€€€~~~~~€€€€€€€€€€€€€€€~~}}~€€ƒ‚‚‚ƒ„………ƒƒ„……„„†‡‡‡‡‡†……„…‡‡ˆˆ††……†‡‡‰Š‹ˆ†‡ƒ…†††…„ƒ€€€€€€€€€€€€‡‡ƒ‚ƒ€€€€€€~~€€€~€€~~~~~~~~~~~~~~~~~}}}}~~~~~~~}~~€€€‚ƒ€€‚ƒƒ„…„……‡†……††‡‡‡‡…†††‡ˆˆˆ†††…†‡ˆ‰‰‰ˆˆˆƒ„……ƒ‚‚€€€€€~€€€€€€…ˆ…‚‚‚‚‚€€€~€€€€€€~}|}~€~~}}~~~~}~~}}~~~~}}}~~~~~~~~€€€€€‚‚‚ƒ‚ƒ…„„…‡‡††††††‡†…††††‡ˆˆ‡……††‡ˆ‹Š‰ˆˆ‡ƒƒ‚‚ƒƒ‚€€€‚€€€€€€€€„ˆ†ƒ‚‚€€€€€€€€€€€€€€€€~|}}||}|}~~}~~~~~~~~}}}~~~~~~}}~~~~}~~€€€€}~‚€€€€€€‚ƒƒƒ…………………†††††ˆ†………„…‡ˆˆ‡††††ˆ‰‹‰ˆ‡†‡…ƒ‚€‚„ƒ€€€€€€€€€€€ƒ‡…‚‚‚€€€€€€€€€€€€€€€€€€€|{}}~~~}{}~~~~~}~}{{||}}}}}|||||}}}}}}}}~}|{{€€€|{z€€€€€€€€€‚ƒƒƒƒ„……„„……†‡††‡ˆ†„…‡„…‡††‡‡††‡‰‹‹ˆˆˆ‡‡ƒƒƒ‚‚‚‚€€€€€€€€€€€€~€†‡‚‚‚€€€€€€€€€€€~|~€€ƒ„‚}}|}~~~~~}|||{|}~}}|}}|{{}}}}}}}}}|z€€‚€|{€€€€€‚ƒ„‚‚„„„„…††‡†‡‡‰…„††††††††………‡ˆ‰‰ˆˆˆˆ‡„ƒƒ€€€€€€€€€€€€„‡ƒ‚‚€€€€€€~{}‚„ƒ€€€~~~~~}|}}{{}}}}}|}}{{}}|}}}|}€}}{€€||€€€‚ƒƒƒ‚ƒƒ„„„…„…†‡‰†‚ƒ…†††‡‡†……‡‡‡‡†‡‡‡ˆˆˆ€€€€€€€€€€€~€€€€€…„„€€€€€€€€€€€€}|~†~~~~}~~~}}}||}~~}|}}}}|{|}}|}}}~~}|}€~~|€€€{}~~~€ƒ‚‚„ƒ„„„„„ƒ„†ˆ‡€ƒƒ‚„…†††……†‡‡ˆˆ†††‡ˆˆˆ€€€€€€€€€€€€€€€€€€€€††…ƒ€€€€€€€€€€€€€€€€€||ƒ‚~}}~~~~~~~}}}|}{{}€~}~}}}}}}~||}~~~}}€~}{~€~~{}€€€€€‚ƒ„„„„„„…ˆ‹‚~€‚‚ƒ†‡‡††…†‡‡‡ˆ†‡‡‡‡‡‡€€€€€€€€€€€€€€€€€€€„‡„‚€€€€€€€€€||‚€~~}~~~~}}}}||||}}~€~~}|}}}~~}~~{||}~~}}~~}}~~‚|}€€€€€€€€€€€€‚„ƒƒƒ‚‚„‰‡‚ƒ‡†……………‡‡‡‡‡ˆˆ‡‡‡‡€€‚€€€€€€€€€€€€€€€€€€‚†ƒ€€€€€€€€€~€€~~~€~~~~~~}}}}}}|{|}€~}}~}}}}|}|}|{|~~€~~~~~~ƒ€||€€€€€€€€€€€€€‚‚€‚ƒ…Š‚€€‚……ƒƒ„„†††‡‡‡‡‡‡ˆˆˆˆ€€€€€€€€€€€€€€€€€€€€„„ƒ‚€€€€€€€€}€~~~~~~~}}}~}|||€~~}}}|}|||}{||~{{{}}~}€€~~~}{}z|€€€€€€€€€€€€ƒ‰†€€ƒƒ‚ƒ„††††‡‡ˆ‰ˆ‡‡‡‡‰€€€€€‚€€€€€€€€€€€€„†„‚€€€€€€€~~~~~~|~~~}}~}}}}}}||~€}||||||}}}|~~}{|||}~~~}{}‚~{|€€|y}~€€€€€€€€€€€€€ƒƒƒ‡‰€€€ƒƒ„…†‡……„…‡‡‡‡‡†‡‡‡€€€€€€€€€€€€€€~€€€€„„ƒ‚€€€€€~~~}~~~}}~}~~||}|{}}}|}~~~}{||}~|z{{|}|}}|||||}~€}|||€‚~|z~‚~z}~~€€€€€€€€€€€‚‚…Šƒ€€€€€€€ƒ„…††…„„„‡‡‡‡‡ˆˆ‰ˆ€€€€€€€€€€€€€€€€€„ƒƒƒ€€€€€~~~~~~}}~~}{z{|||}z{}~}}~~}|{z|{||||~~{{|~~}{}~‚z{‚{|~€€€€€€€€€€€‚‰‡€€€€~~ƒ„…†††…†‡‡‡‡‡‡‡‡ˆ€€‚€€€€€€€€€€€€€€€€†„„„‚€€€€€€€~~}~}}}~~~€||}~~~~}|}}||~}~~}|||||}y|}{|~€ƒ{|€€€€€€€€€€ƒ†‰‚€€€€€€€€ƒ…ƒ„…†‡††††‡ˆ‡‡ˆ‰€€€€‚€€€€€€€€€€€€€€€€„ƒ‚€€€€€€€€~~~~~}~€€€}|}~}||~~}}}}}|||~~~~|||}{{€~{x{}}~~~‚~{|€€€€€€€€€€€ƒ‰„~€€€€€€„„ƒ„…†…†‡……‡ˆ‡‡ˆ€€€€€€€€‚€€€€€€€€…€‚„€€€€~~~~~€€€}}~~~~}||{|||{|~}{}}|}}|}}}}{z~{y{|}}}~}~~zz~€€~~€€€€€‚…‰€€€€€€€‚ƒ„„…†‡‡…†ˆ‰Š‰Š€€€€€€€€€€~~€€€€€€€†€ƒ„‚€€€€€€~~~~~~~}}}}}}}~~~~||}}}}|{{}||||{|~~}~~}}}}}}}}zz~}||||}}}}}€||~€€€€‚…‡‚€€€€€€‚„„…†‡ˆ‡ˆ‰‰ŠŠ‹|~€€€~€€€€€€€€€€€‚ˆ~„„‚€€€€€~~~}~~}}}}}}}}}}}}}}|{|{z|{z|}~~}}}~~~}}~~||}}}}||}}~~~~~€€€€‚„„‡‚€€€€€€€€~ƒƒ„…††‡‡ˆŠŽŽqrruy{~€€€€€€€€€€‚‰ƒ€ƒ„ƒ‚€€€€€~~~~~~~}}}}||}~}}}~}}}}{{z{{{{||||||||}}|||||||||||}}}~~~~~~~~~~~~€€€€„…ƒ‡€€€€€€€€€€€~‚„„†‡‡‡‰ŒŒŽŽŽmkkmnnv€€€€€€€€€€€€ƒˆ…‚‚‚€€€€€€~~~~~~~~~~~~}}}}}}}||||}}}||{{||||}|||||{{{{{{|}}}}||}}}}}}}}~~~~~~~}~€€€€€€€€‚…‚‚ˆ€€€€€€€€ƒ†‡‡‰‰‰Œ‘kjjkkjr€€€€~€€€€€€€€€€€€‚†ƒ‚‚‚‚€€€€€~~~~~~~~}}|}}|}}}|}{{|}}||||{{{{|||||}}|||}|||}}|||}}~~~~~}}~~}~€€€€€€€„‚€ƒˆ‚€€€€€€€€€€€€€„†ˆˆˆŠŒ‘’kjhhigr€€€€€€€€€€€€€ƒ†ƒ‚‚‚‚ƒ€€€€€~~~~~~~~~}~}}}|}}}|{||{|}}||||{{{{{|||||}}||}||||||||{|}~}}}}}~~~€€€€€€€„„‚‰‚€€€€€€€€€€€€€€€€€ƒ…‡‡ˆ‹‘hhhhhdp~€€€€€€€†‡€€‚€€€€€~~}~~~}}}|}|||||||}||}}{z|||||{||}|{||{{{||{||}}}|{|||}}}~~~~~€€€€‚„‚ƒ„‰‚€€€€€€€€€€€€€€ƒ„†ˆ‰ŒŒfffefbp€€€€€€€€€€€€€†ˆ€ƒ€€€€~~~~~~~}}}}}}||||||||||{|~}}{z~}}{z~}{{{{z|}}~}}|}}}}}}~~~~~~~~€€€€€€‚ƒƒ‚„‰‚€€€€€€€‚„†ˆ‰‹ŒŒŽffedd`p~€~€€€€€€€‡‰€€€‚‚‚€€€€€~~~~}~~~~}}}~}}|||{{||{{|||}}~~~}~}z}{z|}~}z|}|||||}}}}|}}}~~~€€€€ƒ„„ƒƒ‡ƒ€€€€€€€€€€~€€‚†‡‡ˆ‰‹ŒŽeeeecbr€€€~~€€€€€€€€€€ˆˆ€€€‚€€€€€€~~~}~~}||}}}}|||}}z|~}~~|}}~}~}{||y|||~xz|{||}}}}~~}~~~~~€€€€€€€€€‚ƒ‚‚‚‚‚†„€€€€€€€€€€€€ƒ††‡ˆ‰‹ŽŽccccb`q~~€€€€€~€€€ˆ‡€€€€€€€€€€€~~~~||}}}}|||~|{}}}}}|{{~|{{{{|y{|}~x|}}||}}}}~~~~€€€€~€‚‚‚ƒ‡„€€€€€€€€€€‚„„‡‡ˆŠŽŽŽedbaa_p€€€€€€€€€€€€€€€ˆ‡€€€€€€€€‚€€~~~}}~~~~~~}}}}}|{{{{||}}|z{}}|}}}}}}{{|{|}|zz~~}|{}~}}}~~~~~~}}~€‚‚€ƒ‡†€€€€€€€€€~……†‡‡ŠŒŽŽdddcb_q€€€€€€€€€€€€€€~€€Šˆ€€€€€ƒ‚€€€~||}}~~}}}}||{|}}||{|~}{{}}|||{|}|{{}|{{z{~~{{|||||||}}}~~~~€~~~ƒ‚€€‚ƒ‰‡€€€~~~~€€€…†…‡‡Š‹ŒŽcccca`p€€€€€€€€€€€‰‡€€€€€€€€€€€€€€€}}}}}|}}|}}|{|}}||}}}}|||}|}}{{{|~}}}~~~~}{|}}}}||||}}~~~~~‚„ƒ€‚‚ˆ‡~~~ƒ†‡‡‡ˆŠ‹Œcbbccan€€€~~€€€€Ї€€€€€€€€€€€€€€€€~}}}||||}}|{{{{||{{}~}|||{{}}}}}}||}~~}}|z{}}|||{{|}}~~~~€ƒ‚‚€€€‡‡€€~~‚†‡‡‡‰‹Ždddcbal€€~€€€‚€€€‚Š…€€€€€€€€€€€~~}}~~}}}||||{{{{||{{{||{{{zz||||{{{|||{{|{{{{{|}}}}}}~~€~~~~€‚‚€€€€‡‡€€€€€€€€~ƒ†‡‡ˆ‰‹ŒŽcccbbaj~€€€€€€€€€~‹„€€€€€€€~~~}~~~}|}}||||}}|||||||{{{{{|{{zzzz{{|{{{{{{{{|}}}~~~~~~~~~~~€€€€€€€€‡‡€€€€€€€€€€€~€„†‡†ˆ‰‹Œdcccdcj~€€€€€€€€€€€Š…€€€€€€€€€€€~~~}}||{|}}}}}|}~~~~~}}}}|||{z{{{{}}}~}}}}||||}}}~~~~~}}~~~€‚€€€€€‚†‡€€€€€€ƒ‡‡†‰ˆˆŒ“ddeeecg|€~€€€€€€€€€€€ˆ„€€€€€€€€€€€€€€~~~~}}||||}}}}{{||{{|z{}~}|}|{|}~~}zz||z{|~~}}}}}~~}~~~~}~~ƒ„€€€€€ƒ€€‡†€€€€€€€€€€†††‰‰ŠŒ’eeeffdf{€€€€€€€€€€€€€€‰„€€€€€€€€€€€€~~~~}}|||}}|}|zzzzz{{}~}}|{|~}z}}|||{{z{|~~~|}~~~~~~|||}~€€‚„‚€€€ƒ€‡‡€€€€€€€€€€ƒƒ‚…†‡‡ˆ‹Ž‘fghfhfgz€€€€€€€€€€€€€€Š…€€€€€€€€€€€€€€€€~~~~~}}}||||||}}|{zzz{{{|~}|{~}z{}{zz{{{|}z|~}}}}}}~~}|}~~€ƒƒ€€€~‚‡‡~€€€€€€€ƒ„‚ƒ„…†ˆ‹efffgegx€€€€€€€€€€~‚‹…‚‚€€€€€€€€€€€€€~~~~}~}|}}}{{||~~}}}{|~|}}||}{||z{}~~}}|~}z|}}}~~~~}}}}~€€€€€€€€ƒˆ†€€€€€€€€€€€€„…„†‡‡ŠŽeffhjjiv~€€€€€€€€€€‚‹„‚€€‚€€€€€€€€€€€~~~}}}}|}}}{{||~}|||||}|||||||{{{|}}||}~}}{|~~~~~~~~~}}~€€‚ƒ€€€€~€ƒ€€ƒŠ„~€€€€€€€~€€‚„……†ˆˆŠijkjkkks~€€€€~€€€‰…‚€€€€€€€€€€€€€€€€~~~~}}}}{{}}~|{{||}|}{{|}~z||{{|}}}~~}|{}|}}~~~~€‚€€~‚ƒ€‰ƒ~€‚€ƒ‚„†ˆ‰‹jjjjloms}€~€€€€€€€€€€‰…‚‚€€€€€~€€€€€€€~~}}}~|y}~~|||||}|}|{~||||}}}|||}}~~}|{|{|}}}~~€€€‚€€€€€‚ƒ‰ƒ€€€€€€€€€€€€€€‚„…‡‡Šmlmklmnu~€€€€€€€€€€€Š„‚€€€€€€€€€€€€€€€€€€~}}~~~~|{}|}}}}}|}}~|{}z{{|}|||||||}€~|z}~}}}}}~€€€€€€€€„€‚Šƒ€€€€€€€€€€€€€€…†‡‡ˆnmoonoqu}€€€€€€€€€€€‚‰…‚€€€€€€€€€€€€€€€€€€}}~~|}~}}}}}}}}}~|{}{|{{|{{||||}~€|zz}}}}}}~~€€€€€€€€€€€€„€€„Œƒ€€€€€€~€€‚‚ƒ„ƒ…†noopqpps}€€€€€€€€€€€€€…‡„‚€€€€€€~€€€€€€~~~~{|}~}}}|}||{|~{{||}||||}}~~|{z}}}}~~€€€€€€€€€€€€‚„€„‹‚€€€‚‚ƒƒ‚ƒnpnnqrrs|€€€€€€€€€€€€€€ˆ„‚‚€€€€€€€€€€€€~~~~}||}~}}}~}|||~}{z||}}~}}}€||z|}|}~~€€€€€€€€€€~€€„ƒ€„‰€€€€€€~€€€‚ƒ…oonprstu{€€€€€€€€…Šƒƒƒ€€€€€€€€€€~}~}}}€€~|}|}~}|z{{}~}}‚€|zy|}}~~~€€€€€€€~‚…‚€€…ˆ‚€€€€€€€€~€€€‚…qrqrssuvz€€€€€€€€€€€†ˆƒ‚€€€€€€€€~€~~~}~}}~~~|}€~~}|}}|{~~~}{z||~€€€€~|y|}}}~~~€‚€€€‚„€€€…‡…€€€€€€€€€€€€ƒ‚‚‚rsrsuvvwy€€€€€€€€€€‚‰…€ƒ‚‚€€€‚‚€€~}€~~~{}~|z{|}}|{|}}}}|{|~}||||||{x{}|~~~~€€€‚€~€€„ƒ€„‡‰‚€€€€€€€€€‚ƒ‚‚‚rrsuwuvwy€€€€€„Š„ƒƒƒ‚€€‚€€€€€€€~~€€~~€€~}}{{{{{{zz{z|~}||}}}~|{|}}}}|zx{~}|~~~€€€€~}}€‚„„…ˆƒ€€€€€€€€€€€€ƒ„‚‚ƒ‚‚stuvxwwwy~€€€€€€€€‰‹ƒ‚„„ƒ‚‚‚€€€€€€€€€€€~~}}~~~~~~~||{||{{{}~}}}}~}}}~~}|{yyxxy}~~~~~€€‚€€€€€~~€€ƒƒ‚‚‚„„††€€€€€€€~€€€‚€rtuwxxxxy}€~€€€€€€€€‚‹ˆ‚‚„„ƒƒ‚‚‚€€€‚€€€€€€€‚~~||}}}}~~~~~€~~}}~}}}}~~~|}}}}}}~€€€€€„ƒ€€€€~€€…ƒ‚ƒ„„…‰€€€€€€€€€‚‚„ƒ€tuwwxxyz{|€€€€€€€€€€€€„‹…ƒ„„„ƒƒ‚€€€‚€€€€€ƒ€€}}|}}}}~~~~~~~}~~~~~}}~~~~}~~~}~~}}}~€€€€ƒ‚€‚‚€€€€€€€€€‚…€ƒ„ƒ„‰„€€€€€€ƒƒ‚ƒƒƒ‚€wwxyyyz{{}€€€€€€€€€€€€‡‹†„„„„ƒ‚‚€‚‚€€€€~~€€€€€€~}||}}}}}~~}}}}~~}}~~}~}}}}|||||~~€€‚„‚~€€€€€€‚„€€€ƒ„ƒ„††€€€€€€€€€€€‚ƒ‚‚‚‚ƒ‚€‚‚‚wxxyy{~€€€€€€€€€€€‹‹†„‚ƒ…„ƒ‚‚‚‚€€€‚€€~~‚ƒ€€€}~€~}}}|||}}|}~~}}~~~~}}}|}}}||}}~~€€‚„‚‚„~~~}z~€€€€‚„‚€€‚„ƒƒ…‰€€€€€€€€€€€‚„‚‚‚‚ƒxz{|~~€€€€€€€€€€ƒŒ‰…„‚„†…„‚‚ƒ‚€€‚€€}}„ƒ€€}}}€€~~}|~}}}}}}}}}}|||}€‚ƒƒ‚€€‚|…„|}}|x}€€€€€ƒ†ƒ€‚ƒ…ƒ‚……Š„€€€€€€€€€€€€‚‚ƒƒƒ‚ƒ„ƒƒ{~€€€€€€€€€€€€€€…Œ‡……ƒƒ„„ƒƒ‚‚‚‚€}}€„„€€~}~€~||~~~~~~}‚€~}}~~~€‚ƒ~|ƒ†€|~€}|€€€„„‚€€‚‚‚…ƒ‚„…‰…€€€€€€€€€€ƒƒ‚‚‚‚‚‚„ƒƒ€€€€€ƒ‚€€€€€‚ˆ‹††…ƒ‚ƒƒƒƒƒƒ‚€‚‚‚~}…‚}|~€€~~€~~~€€€~€ƒ€|zyxxx{~~|€ƒ‚|}…‚~~€€€€€€€‚…ƒ€€‚‚ƒ…‚ƒ……‡ˆ€€€€€€‚€€ƒƒƒƒƒƒ‚ƒ„ƒƒ„ƒ€€€€€€€€‚‚Š‹††…„‚„„„ƒƒƒƒ€€€€€~~€~ƒ…€|}€€€~€€€~ƒ€|xwy|~}}{{z|€ƒ€|€‚}~€~€€€‚„„‚€‚‚ƒ„…‚ƒ……†‰„€€€€€€€…ƒ‚„„ƒƒƒƒ„„‚„„€‚‚‚‚‚‚€€€€‚€€€€‚‹‰†††„‚ƒ„„„ƒƒƒƒ‚€€€€€€€€~‚}†}~€€€€€}|{}}€~ƒ€}yy~€€€€{{|{|}€€€€€€€‚‚ƒ…‚€€‚ƒƒ„„‚ƒ„…†‡…€€€€€ƒ„†„‚„„ƒƒƒ„„ƒ‚‚ƒ‚„……‚€€€€€€€€€€…Œ‡‡‡†„‚ƒ„„„ƒƒƒ‚‚€€€€€€~‚|‚€}|~‚‚€€€||{}}~€€€€‚|zz€€€€€€€‚{|€~{|€€€€€€€€€€‚‚„„€‚‚‚ƒ„ƒƒ„„…†‡ˆ€€€€€ƒ€€‚„††‚‚„ƒ‚ƒ‚‚ƒ„ƒ„„ƒ„‚€€€€€€ˆŒ†††…„‚ƒ†……„„„„‚€‚€€€€‚‚}}~||‚‚€€€‚||~~}€€€€€€|}{€€€€ƒ|}‚~{~€€€€ƒ„ƒ‚€‚‚‚ƒ…„ƒ„„…„†Šƒ€€€€‚ƒ€„…†„ƒ…ƒ‚ƒ‚‚‚‚ƒƒ„…………„ƒ€€€€€€€€€Š‹†………„„…††…„„„„ƒ‚€€€}€€~|}‚‚€€€€}}}€|~~‚€€~}}€€€€‚„„ƒƒ|~ƒ|€€€€‚‚€€‚…„€‚…†„ƒ„„„…†‰‡€€‚‚‚„†…„‚„…ƒ‚‚‚ƒ„‚……„ƒ‚„†„„€~€€‚„‹Š†††……ƒƒ…†…„„„…„ƒ‚‚‚‚‚‚~€}ƒ‚€€~}~‚‚}|}ƒ‚~|‚€€€€€€€~{~€|{‚}€€‡ƒ‚‚ƒ…‚€€€‚ƒ‚‚…†„‚ƒ„………‡ˆ‚€€‚ƒ„„ƒ……„…„ƒƒƒƒ‚‚ƒƒ„‚„„…†……„‚‚€€€€€€€„Љ‡‡‡‡†„„„„„„„„„ƒ‚‚‚‚~~ƒ|€|„‚€€~~…„„…|}€‚‚€~~€‚€€€{|~}{€}z~}~„„„‡†y|ƒ„ƒ‚‚ƒ„ƒƒ…†ƒƒ„„……†‡ˆ†€€„…„ƒƒƒƒƒ…‡ƒƒ‚‚‚‚ƒƒ…„……„…††…„„ƒ~€€€€‡‰‰‡‡‡‡†ƒ„………„„„„„‚€‚‚€|z‚€}€{~~~~~€€~~‚‚‚€~~€~~€~xx|}}}€ƒ~z}{{ƒ„ƒ€}y~€„…ƒ‚ƒ‚‚ƒ…‡…ƒƒ„„…†‡‡ˆ‡€€ƒ„ƒ‚ƒƒ…„‚‚‚ƒ†††††‡‡‡†…‡†€€€€€‚ˆˆ‡‡‡‡††„„††…„ƒ„„ƒƒ€‚€€~z~€€~|~€€~}}|{{}}}}€€€}}~€ƒ€~~~€„€‚ƒ~|z{}~~|z€‚„„ƒ€‚‚‚„††……„„„…†…†ˆˆ„€‚„ƒ‚‚„ƒ‚‚„„‚‚ƒ‚‚„††………†††‡†††€€€€€€†ˆ‡ˆ‡‡‡‡‡†„„„………„„„ƒƒ‚‚€€€€€|~‚ƒ‚€€~}~|{{zzz}~€€~|}~„ƒ€……~|}~€€|{{{yz„…„‚‚‚‚ƒ„††…„„„„„………†‰‰€€‚ƒ‚‚ƒ„…ƒ…†ƒ„ƒ„……ƒƒ†††…„†††††‡†€€„Œ‡‡‡‡‡‡†††„ƒ„…„„„„„ƒƒ‚‚€€‚€z{€ƒ|~~€€~€ƒ|z}‚ƒƒ„‚€~{}‚‚~}‚‚‚~{{|€€ƒ…„‚€‚ƒƒƒ„„†ˆ…„ƒƒ…†‡‡…†‰Šƒƒ‚„„………‡……††††„‚‡†ˆˆ‡‡‰ˆ††‡‡ƒ~€€€‡Š‡†‡‡‡††††„‚…††…………ƒƒ‚‚€‚‚€‚€}}€}yy{€€€€€|{|~}{}~~€€{{€‚ƒzz‚‚‚€€‚ƒ„„ƒ‚‚ƒƒƒ‚ƒ„…‡†„„………‡‡‡††ˆ‰‚€€‚ƒƒ„………………‡ˆ†ƒ‚†‡‰‰ˆ‡‡‡‡‡‡ˆ†€€€‚‰‡‡‡‡ˆ‡‡‡‡‡…ƒ…‡†††††…„ƒ‚‚‚ƒ„ƒƒ‚‚‚€€€€€€€€€€€€€|zz{}€€€~{||}}~}{|€|}€€€€‚‚ƒ……„ƒ‚ƒƒ„ƒƒ…††…ƒƒ„††‡‡‡‡‡‡‰‡…„„„ƒ‚ƒ…………„ƒ……ƒ‚ƒ‡…†‡†„„†‡‡†‡‡ƒ€€‚ƒ†Š‡ˆ‡‡‡††‡‡‡…‚„†††††††……„ƒƒƒ„„ƒ‚‚€€€€€€€€€€€€€€€€€€€~|yyz{~‚€‚‚€€€‚‚„†…„ƒ‚‚„„„„…†‡†…„„„„†‡‡‡‡‡ˆˆˆ†„‚ƒ……ƒ‚€ƒ…††…ƒ„†‚ƒˆ‡†‡ˆ‡‡ˆˆˆ‡‡ˆ„€ƒŠ‰‡‡‡ˆˆˆˆˆ‡‡†„ƒ…†‡…………………„‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€‚‚€€€€€€€€€€€€€€€€€€€‚ƒ‚‚‚‚€‚ƒ‚‚„„„ƒƒ‚‚ƒ„„„ƒ…†ˆ†ƒ„…………††‡‡†‡ˆˆ‡ƒ‚‚†…ƒƒ‚ƒ‡‡…†„ƒ…‚€‡‡‡‡‡‡ˆˆˆ‰ˆˆˆƒ‚‡Œˆ‡ˆ‡‡‰Š‰‡‡ˆ†„„…†††……†………„‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒƒ‚‚‚‚‚‚€~€€‚‚‚‚‚€‚‚‚‚‚‚‚‚‚ƒƒƒƒ„……„ƒ‚‚ƒ„„„…†‡‡…„…†…†‡‡‡ˆˆ‡‡‡ˆˆƒ‚†…ƒ‚ƒƒƒ„……ƒƒƒ‚ƒƒˆ‡ˆ‡†‡‡‰ˆ‡††…„€‚‹‰‡‰‡‡‰‰‰‡‡‰‡„ƒ…‡‡†††…†††…„„ƒƒƒ‚ƒƒ‚‚‚‚‚‚€‚‚€€€€€€‚‚‚‚‚‚‚‚ƒƒƒƒƒ‚‚‚ƒ‚‚‚‚‚‚‚ƒ„…„„„ƒ‚‚ƒ‚ƒƒ…††‡†„„„…††‡††††††‡ˆ‰†„ƒƒ…ƒ‚„„„…†„„ƒƒ‡…‰‡‡‡†††ˆˆ‡‡‡‡†€~‡Š‡‡ˆ‡‰‹Šˆˆˆ‰ˆ…ƒ…††‡††††††……ƒƒ‚‚‚ƒƒ‚‚‚ƒƒ‚‚€€€€€€€€€‚‚‚ƒ‚‚ƒƒ‚‚‚„„ƒ„ƒƒ‚‚‚‚€€‚ƒƒƒ„††„„„ƒƒ„ƒƒ„…††‡…„…††††††…††††‡ˆŠ‡„†††…ƒƒ„†…„…………„…„Šˆ‡††‡‡‡……‡‡‡‡…‚ŠŒŠ‡ˆ‰ˆˆŠ‰‡‡‰‰ˆ†„„……‡†††‡††††„ƒ„„ƒƒ‚‚ƒƒƒƒ‚‚‚‚‚€€€ƒƒ‚‚‚ƒ‚ƒƒ‚‚‚‚ƒƒ‚€€€ƒ„…„††„ƒ„„‚‚„„„„…††‡‡„„…„…††‡ˆ‡†††‡‡‡‰‰ƒ‡†…„„††††………………… \ No newline at end of file diff --git a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg deleted file mode 100644 index 20b5a2c0df5209ebe821d4220055012ac6d94993..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20193 zcmex=oA#DyHnP8$!323`E1Vw_ae#K|QlE+HwUs-~`?sbyknW^Q3= z=I-I?6&w;879J59m7J2AmY$KBRa{b9R$ftA)!fqB*51+CHEHscsne#9glAUcUPH>GPMb z-@gC&`Ro5J1`bfL*)#mE4onHPJpRS>^!*bHyH*}NFFN_}l5Wke0=A0tYg1oMQ{%L` zaa*gBr7B!-?W^;08(D%iI47zI{SqiR{O5OgU}o{osxRwWdXBNjbWQd@_a}L2X_@y7 zPG-3;2P~5czpn8Ab)YsgCE&n2(U`_L%wI%#x~~WrSzndBFiqAlG_7Tt{nDW7oOvE+ zgJ)fsd~m(SkDI1nx9)E*S*?9?4d3=Pj)}HMH|I`GRJ?h}{KF&>p5L?1%QNkK2}YyNQlk-y4Ld#?XcUt7_z_ukyq{@JN!OMX-)vgEQkA2zU*ne%0R zY*U~e*SZH)f)}p+>s_+-aL+pJts5>+yJa)+N0@z=Y3P>Li5|RNPyg61+57eStyD?L z6KAVL*Y4;wx>FT8g<*16TY!|m*X2uV%gqWWMwd(HzMZ-8{MyRn9a|N3&dn`8mYI9t zx0TZ8#@l;CDw*PBCVpCP@|Nw6-{m_K59Tmm+F37sx7R&n)1{C}J%>CC|6aWPaoxU4 zsonlsD+HB&w0?0t`#PWV_K(X4cAcAC#@O}F%;ElB^>^DJ6lCv@E&Futop<##y@eaf zL#~H&Yvl)d{0r?~xqRNz#*=b$v*q{&b$qUsyS#mKd~4wK66F=QB`&lT{=S{b?sWg| z`Gx-(xVk;3oXlFfiH}jMs`*ZMX>Mn3;Ky?p!`67*Zz!nEUa>VvcVpjGq5bUgYaJC% z%562Y;nUJo3X_yL8=AOfLh<~nrFOC(s;feBEWWKO(rQylxneAP%Hn|S>r?-OUtJ7 zspMTLvr1*z!sc_f-`4MweJS+naEQQ$t9CcG@6X|>IeWNa-|VtKJB~ZY)qXhca&hZ? z#)^5b<2G;EeR-YfS+{3ldwN7eh2Gwsx489f=$b9N=d=Fc5B^x*@>SN~d-LUYKHd-C z#{B$nx>M9?g}q5?GY>(Iq8$epT)sZJYlL=BC#&udU4Id&AgxZ$nM+ znvMlEW(+b(dv0k)eS2SNDfuFMX`8@=_qK&G^A?x+9(gHOb6ruSHhU4zEWO@MZnp%D z&SW=!*!DI#a`%;|Ppp#+su?eTS@$^L=)CEQFCr2`*9tMrQdx7CHkIe)g!ms=@v!Xtl1n0-gd!d17nOqeXwWn}m|W?J`0rq743>*p6r%naRd z(ev`_Xt95aZU4SKiulh^wyExe`m(BjTVGi59=~Ps<9y(ju)9qR4;l}?juqM%VJ-8) zFn}re*)d)P#@jq@zE*NAa{IQNw6N8hdQtMYssB?x?*Na3$`jv*O3BaMc=DTQScz6$ zapUc-Px&1_)8)nMByTrQmd-Mlwcnk`{_kvTdZ)=s)BF5cR#`Eoi|^j{WV+-2eUZ-6 za{gt-@iIS{za{P6_+x%?uHDL~t_#lR7w9E#FBO+*Q!;XpWMS~%U-g^A`T4H5ah~Vc zvp&ynlHC6wLPE~pW)|+?-(&sY;{;pnG<%lF?R_TvY?17f zy*BNdd{nB-TKsm~(LE}YJlTA<+fLeh|8J17fNgf?N3~^_PXux(FJo12|MF>pr9@Kp z#_fqbwnkSYPEXf4@bS`wnEd2V30#*iuDupnKChrE?AXdO-M2S{M3VNbKeB7HzoEmC zrUI>P69p!PFg)J5Zrax~WlNebgexwHmUWJwS9Y?CdGfof{*T;tM5$bUm}B#trGE3h zEj5x$bi@i9Ppvrl>*{>L`<2@TA{V?f@-11woy*gB{N$xw{bHtj5?%b(Z+RkWxsE?% zzSiUIRS!!an;87O_P$}S!~2je>t$w_99dk{x3&M#S%0sOQZwI5IMy^zykN1e-u=4V zy^o1Wl|jaDomdTxm9FyY{oI3Lw}Jv-s5b;Bm%roz6jPwg~X)qZ?_aKk${ z;!Ccd?}4HvGbSs4Uhz-5VshL^)30BwmfsAsRgu3(oY|MUwmoz z%G{&pV~-4blgb66K7B_fe|fg6 zzB#IJ|CVbu$t6A8?NoREcH#JA5*FAy?eyEJtmpSky`H_Rvx`0C!DG>rQ8#Aq{BCqT zI5}&>0&9-rS+hL-6&+U`l3@s$zczl$W*+0sYjdMnwn=w2c0KtMzAj}GJ45)~*x1VJ zD*6_tyB8f%mi|0Byi|Jb2ZQr@Q@azZW|hr1exm1Dl-+iD@${o|%GL8{tb3zp&3I{V zCR3(V9>Y`j*D=r9g|lW|pLIZh&vV`GBgGs#l8y&$_^VdvscoCE=E#JqXB$gr-`_Ni z_cN2x@{OX$@7?bdS^26;>QQM%%BGn=7*(Dm?fC2V@Ijf@ld4%~E|*@K^>>rl$(;VY zsbBINN*Q*!s0g;G?ae=YY3--gnk;{_7fo_?<%*toeb%|1y91|&>joY)NT|&^wC%ad z7g1$_*F9H!*|$r)4RPJaEl@S9E$ma{d9_vTpE*@Nhn{@$Y|r~g@`q=i7k>3CZu_Fw zn=+)H&6#vq^g?S|pGV5EA9wi!r?SW$+wnI1aJ+;GxBG0DtA8xlo;a5>>7K96ZR?Hy zblMKu@GS|KPye|5k>A9cYxA@=U)ZCm_I9=2>hzKg`*%#`)_)y0d8vMXP2sY$qVqd! zq`s9K&NzE-mikV^)l8g$yICGD%T7NwPqO`RJg*6VdzH=_m8-LOx!7$i+9h{Pd3?#V zsP;zqnl%A0rxo5lDAC0c;4Y={FocKkCu7XM)dgxvjh-jpM$MDE_-Xcy3AV2nh-I)b zuq~c-)t%ApeO6M`%A_*}T8|hx&Q+}GjStV5`fXleT;{s0T$TQv`;w}!+M3vyOCno@Hwvfk!WmzO`Gv{g=e1jgfO5%3f$4@A#@V z&0o+RtvOT-#BwSXaD$#X2W` zv8XJL+q&kya(BF=^GmwIlRnUd}1$#CH7>$t*oek;~- z7tY)Itp8EQf{&?9l14UPW2#af?c4IBUNM&Su*{~Ne^#%$ymfZBnXX7{wC(DJ%}2_G z4b*~nU)s9%EBgc{zv6pa-o6vPE@9sqzkT}C1D0}MGbNJcPpv&FX*l`HmE7#9CuD20 z=3GH71F+fbJ!NHQJTlrlyNB|qUNMl zTbSH~>+1C%SVZG^6P4|drE!5CneT$e3e)lvy$aksP|hRx8=`{U)r%ZIYjS_XeaZ^bwN+} zT{g@+ynk(Wrr1*nLkrOj5^if86t2nbubGhfo$u_{P*2Ns%m=S+`q3Owy`1Hk|0#|a z*5PZ<8gAYADM7L(LGae97x$#+W&XQdVEJ~|>m{Yzw%?xDytR1R%5QsJmlyFA&NF@; z=e{zl_~rDxQ%^l?eurDGy0d2Kx3&y~CT`o!`ep6x zT;C*Xm-+e|*Y?^6eU{hwVfSp8b@blDtc`Y87jqssmmPPf?z!c;tZnO8s7+sdKhCpX z?-r+;y=&0@OZ)Qv&fT9STmR^N-~J7zzWjb$8X`}YTn}5X_HcV1+clMvcxBe~ZNExem`k>s2eI_B@3cR?wxX>6cfybGBXt^5^6h`@*Jp1RI{EaV?%Yj` z=BruLSoC)?_f@Pr|7iQMz041Nr%ko*be zR*(M-<&SInUd#}t3kxy$}R-U_b2lF=TSs(u9fBX5Pe(yAG->OvEql@%EPue-{@9sYWsYyCt zcN|a3o$zm7YE^m1{tRC6gY%f$k8WS|B*I@tUw*3COZzIL{d%?hHXq|0tA7{#2oIPs zb?23Ba%a;j zmmK{vyCX!blEHyJcyibE4|N^8nT1y{r3kt&SR`CmbkSJHvF!^rv?i{)Nt8 zpY5f1Qx<@<;m;KeQj2`f+Rj#1%%z*Bn@UhWpT&{rBTvS%<#Ni@JN_ z{D}&M>$a<7PUf8Xxc`)Iarr#MU0ZlB_j{e#@nf^>9-Yf~E4}u5Z(}WtQ~kRjH%Ptd zUZlQcn|=9r_s4PHe;Zd!-FlzRxmv09uJ*Cubq3itmvwAVFJn)t4wY1|c=Gre^X08s z5%(88?AcQ#b-g{(v1xrz$@8@(Qsx~W4n}T#KkMC>xt@6ef(q&j)~?$CZi`xK&X)o6U2dPL~Y#h2#R9&dbd?AO-l{QjgS~Q(orgXXfVZ zH@P%Z;O%)=i8#*}w%Lof?EBI9JE3BSXL*sXiMFPZpG0KD$~UY3Gw7-)U!VL&`;C8V zJntU!5A!|Ds#s^38!ZsG>iLn~-TdzE^uH62U)xvpcfbChCm%w$y`Qr6&JsTR=M(-j zNbUa6{w?T(eeXVzisHyC^F!K$_iZ!w)!lj_RK1$9RmQcKTE@5$yXeThl#DH+r5|+xT>E@4Bl`6IQhs>fRPTU$OX6i3absIq%L`ZFmr--?k^X^4o{& z!lw?Nce?uOSL)l@?zmTAw3)_A<}-{M<)^AFv#-~6#`?j#rUkNx+fg-?8s5B<-eHh;xE*3QEdlD~dg zul@0OkI7j0G_=t85vWIoEH>Z*p5E zePD9*H1*WmkLL$${gPt-{KSoD`?y7$-@U(e@wCQ?W0q@9mL9K}WV1ifo4<2ObJKCA zW!pc_S^8=H_ItbK`Tw{~j0{Xk_}8|5>we)ouf$eGrD^v~eG)Hkx@^-A^+(skEGL~f z=J-8T{@OmRa-J;O^}Lb)84l@{9=#A_d|P+jWl7e)M+<&G+jUl~zJK%mTgOf86f&Yx!tdtH zxIJ;soRXte`frymUE1IJ$B@Bh@0&C3>#i{WXGmMie{=KW{|t?Gk~KP8E}5=eA0FKG ztTIlzT5W>B*{O$9_?oY6uv7W*Jm&ApJ=x1MeqM|0b(UApOb3bf<_;2#V6U#ogPKlV5VEF9Rku81f2@!LulQw-lo0vX1i@BlR>U7tG zX)5#2EstEBBc|~9<7(~_r}X7ZgJ!+g`kC1OJYsv*M6JX73S<0xOxXn&DVr-imwnyT zl&pHVaNeEp;KaOd8@f9^0;>4FWYuZSaL|1?q3L;EZT5l>rdyUPsp_mVH=Z|j|0CPX zEj!Fyj#sR2QJ-C4z3XbPx##Z4Ngqyp%bIUwHFsUzys52^_ji8(usZWw`Rt|tEH6A+ z@bAUuyy$lNd>G%Hdmo?h2)9#ct@o!jjGI?Ueuj;F7tsVDX;ryhOF1H};>+)vP zYUgK_d=BqeOFYi!rfl@_+GhQ2z5I{=45ml+^VB#dvjoMQH&WtQ=yb+?#*zp8iE-(_ zHEx_;e*96{?yb}A&bYm*Gg-sW=I}As(7%iA1lxYQcYn!f%-_2*TH%%b^9}O9#Gk*8 z6@T;aHW_jFUa_v^W7`}T6)9g4Ib#k3gUzJu zGxd3EO%*nNNO5I1x@&KD$nbCWj=!`1Go;72|5NuJ&d>ZZsXSH`@!s!?aS}G`BJWX#F|GDSN;}W|G3`zqx}(&sdGi<-uwFe`#!U2 zo)W7Q!m2BF6>#S(U!LXiaqR_*`~Ml3n6@nVc=q(Fwkh-1M&yf#_jejad|wql>p#Pc zznf#qySAQ8x+-K^f7gat`~Bpz#)k|lujjnk-5zso-riO3rl&n%`W3GJZF6bK8NR@Z zb-xeF>F(aJy}Drb!hgT{+#iR3JNwAiIYYW>UiPE4_94^mJwMmucFykojNJ|2k7i^^ z?LD_tdgeO|<*6}@ar!GfSTPc-jKUcxxb==wCIw|(P1*N3ky)Vmy2iVllUId$NPK)B2` z{oeZYwd_ZKt-k-tY`Rd~u2m)*{Z1ZuPDb)TIyOZ!-IT zIN!15Z4z(T&wJ+*jSdu2wNHqJZu z@bCLeD>CmAiedUc1-4WJJlWgxtFhA6L|BSQrJDmq`IK`mXcLLKc+kU$bYz6 zpZ}%wqm3InoLPK+-_i4YGHO0jF=)d3e$k}?GcSYX9_e}+b_TQCQ zuGVXMAnB6e3&YSSm-TgjN*35hrI_QXp`)`?h(r0z~N^Y%nloi<1 z$e?b+b$e!Rjdfax-OC#ZkGB>|En9WzYeM$HnN{cK84@w}}!VpnBOaL*L# zH(r_eIUpYlXI8_!%$DaSe8#cc z51tq5=lbJzVXL@BpY@b*_1{Y#{A=4EoKupmTkX{s5_(!gp)uy#!iSbyUiV&H=kP`= zJ6l}CV5Y2xt!vK94NEt7N|dZ?Iv@2p#c(0x?8dWcuj)BVYb(#3FIi&m`enWSkIBm_ z@*lL#7k%IHKI+}S0^enm^W5hb_AQ?$%3fwu>o5Aa?7sXzrHXCOJoczBaL*N~Rk*z6 zaivoKx4VtcKZsVH{x#a?LP>|2mVq0_`I!7 z??ry?BFnZ%GTdwJo-7?z+Wnti{~>hW#$f2U`!jsTY1Y-FNwz)|$HJW$@qi{(Jq6B+I6%r1S_> zvIsb`?OGkr8?C{7I>^1qDong%pVklVhyPZUoGm|67FD)LG~$-kZV%?#%dNrJ_KHer zOpskXKd|fM)jgiM%U#YqU!L_d{jvSq$qOpXk6e$F+tD2`?U}~Kxo?x*dh#ThRU6M4 z-rBjc#^avC_O`#Wv$Q*|h1|WK&C~xpF7%`Sqc3s{d$(@sx{_snY5i;+U%i7u^?V-e zlh4aF?e=fGryytkVeY(bxpv3$B(EHjuZUd}Zh7F#x@X5dvirXlrfrFKnRM4Phe_M4 z!pqPnZByQsgrbwjt4_{46>;#u`K4(wor`j&C}y|&cT9JW+#?pAFXH%T>#Yif!iiDy zKTPngmYnt|ePX+1vfrg8nM*V8uY5W0(C)oVfjPZ;mCTLNQWn2c|1-=rR1KfE_x0s1 zt9STa4H9pRpY*oC!`{%O+bsIZ@5{1%6)XBh4rOXZSMuaZ`f(^U$1ktn%zni9(R!v# zbL-GKN0cYXzh8gq{%QN(>Ma}GD+SX!nEM>oM4GHvG_@m$J!|obNb`w`4*oJ_>l9v1 z7YnzZk*igGePxCHBadvQ%N|_{PVXeXG6)s^;QDd=VeaC8SM6or<<+>?S~eA{e!X}6 zu<9lOA&&DO`e%mUNcp<{!hQZ9)!rZWO=O$BI3oJxtQsyc>*|Pa8sER$J)a!;&*Vc` z??-0m9T&geR@uL4YT-7=&x&tO+zo9{(w_3e@V9M6@q?#(55GK?{Wk20$o$}42PRIo z6kH*r^}%lW%N)bbp5)MUh?K`!L5OB7Chiv{>*OcSNS*EkLPcF5p}#pZ~1-G?!UV(dcKyP zpc44+^;`Q7#_CuOQvpjy7hH$2m7)Uca@L-v#PiHoqXh; zdDmH$!*f1Wm_FL4^P|)B#^mp7WgLVe`?(gcv|n3S@yF>S&py46X`9!zmNh+k(K4Z@ zVNZ+X*IPUPXur76Tk+|z*ALs}KibOgi|(D}k}MmSD$SC7XTzVJ>!+LEbr1Zg`}f=S z3OTnO^S@6{eiE)Qq4_dHfh}w8_TPp-s?YzhUuYOm{`33~$#~JXQgze6Ie&LxzqB^~ z#iNp+M#V29KK)2hnsnpD8t=Ob3l$la7PKF}bVp7>{c_jG`?tc6{b%^FwnpvRgxvnf zD~(-u_%^Qpc=87y`_6CcXYb*El&Pm2sDI|b!;aVUqrO^K-@UeC!QWS}Om?5Y%X;9% z*LC}ycJ;kV-Cj9OaNe52SzlwNy)L(Ki8;LOi;%j#{qll#olTV$@BE|wOi9(Sd>M56 z=u{#3-Iwn*&#U~;VEan>sL4Ekn+v>_uC>v>g)8>G_uF~3l1nnRMe! zxH|h&?Hhe9;iv7GK8C)3bNJ(n^VKr_ON2gHD?K@#uK6>z)@#C&{!pd;?tdgdaveYN zD|YMO-Z$mf%(>*OyUXtM*gCGAFI3}jVUOS<-!-qRdcw>EKR!EE_qi-I@U-C&^ zE1J$9k&0cnG_g|jYU>rJj69`;zzbh2|JBW~d>KD~{?_@&!+y+uwEoRpwFmoKx6I@^ za;jqY{0W!kLuJKptM9FT|K@)3htG%pGw`}i&wrFF9)6|N;?LR0Yac~+8%_PRZ6k}8 zw&=@~I@QD>r`<&0n$p;>&f9x4GCQ zXm~u4zteS9}$X~Jnv9?{S)n1y4gp<&DS_uvV0YNx?l3E=a&8L zQg>cFRhgu^J?uY2ZPWYx*ljK|Fxo!9t9ua;1+vn2VbL_uf zp3-~#5_^8Xa^A*1Z zy$#pi)jklH!EePEI%d+9Er_D@Ug->r~F|b7J#+AocyO6 z2eOa;)BGc|`Qx|Q-+a9+73T>>-TJ1=`Y`$WtK~iGKiOPA_x3@5cRlO#DH-Bb%Wpi% z`T0ww=i~m}?kCw_*$YS83H~Vl82{LAH_NRPSN?1$xHWCxyN!qTep_8s*JZEu%m0z- zytN<7AM9P;P$QhX_I0_{#EI*-YAl%KD3aT6q7mSfZ^J6f_IS>m>*^oezeQi(xILb5z}%+xa@_F`^^f<= z)A?bZcG-KbvwP3YSVNl)y~Wb=SLOb3T;E>9`B>wR;$w@gzh|58H~hIq$(_U7LfKN` z%gOc0+y1V4%lQ1zrR~?g@c*5(yI(fT;E&(m8To%a`R1AYi9ascaPpbpri7h$8?W0< zy<2x^g7P2hYj3};OSo!Ye?L(DKZ8*Ag_8daH@CZA-e-BI#;L_+!ya9&8*d-#M?cMM z&k;TOpP{mIz4hPSCXIy;?D_9C9+8^Z^Pl0sqx^qLyZ@TB>a$ET<0<<5*|Io-kZh19# z5A#x=FwX1eucQX|p5i|(e%WUJ0?`lI`yLesKAxw2VGr|1rfQ3D-(5Ryt^T*nlRy96 z{FiY@7ghAGn^>-rw}wrI=VR=&sg(xGSxkF7MDM*YydRsuuGPM+FgEC3W))9d+S)Zy zH&?t;pXpZ5l>JOL{hRbh*F_sIm%ddGQh1_r$b(aUGXJ$b?T^?G-WRJeig@}_Y=*Ax z^Lj1Khf`BKZ?ilQwJqR$erZS8tD-FBOB47s9gf;o{tCY~e{3BPZvoXU!HYvTlHDJn#vZ2>hJ5GAFk6b2(wg6eSh$E z?7WX_+&HW|znF?`T>r{Sb^b&ac7_DesE_A6c2(6lUsAiY$2lQiN6K~bDV@`wS6X{0 zG+E7hY@_`l-SxvAsn&V@%YC!i7EAQKwD~Dx^srsJ^EgkXsM`GB3P1P?r(amY{VMO- z`G2jkX;Xj8{xM@LugHw~&#<-F@ccjfxP?nXZMp|AhPVyzJX{BYUOaiSVm? z>zdcrrPf{9ZtVY&>B@PdDNBtcS9E1Z(D-{EOC9QI`J^EN|ntS=b zFD;XQ&iX4^{h(n^Pj&d2`fYbVPCslX*<*S9_R^%CkCp7~Xa0Hpgw1B2mXhUS*_RH> zSA2c8@8RDWTaxaxC{C8~FD|(G?S%ZLEniNY;ghJDbnK?YSJ5x}ht`Y!XUK}=xSdh@ zV13Q?HMcA)PME|fq&zI1b-udx?57^7o`}%dz&74aqX9H8|R&n zT-)=h?RSiI*G!QFQRSzql@Hr(q+Z%-SNuLG&z*ERuf|vQNX!=tZr_V%64;qh52{a8UHXpIIT`{ z=e+yDd-{(QbZ&XLJo4vx4MmS2b0&%RahA(6=S%NVHCvgVxS~F*BI?u2ZPCj+w!GAt z6!!1ciTi4`ouV&-UrshqV4k0~=vT=pzrg)kO}0YWZO@EPu9eQ`6z=`Od~eI!E!^JD zJN`VsEED={o^-^?pMma6lU~VbH?by4@?1UodY^i|nrBMx3bsnq&*|M;XYL7pB(|gY z_m2Mze05_ zk^kmmznj19U+{?Ys?T{MxBUCsf1=H+R^NOT&c(>!ai`AoLIwZP{D*I&EF&g!PmrHC z@9Wp}Hq#UD-zt};rY6qmy0K$Vw**J=jcet%6}Bd&N+jP~vhCX2;zos|(NcRC^UMqp ze3iBJc*mEc>?N<j&Dkh@UtzrAzw7oQZ)$8m{Cn(kY2B18tJBh<+!Mm| zHf?{}`k$fk`g!fg@45e&Uj4)Vh@a(^e(~x|!H!ao79khCgC~o;8TiVrYR#&`cl^6N zU##T4#2>p4xp%kB&)!@cW_eIqadF0Ng?}=Fk^dP4{{;PKU|R6ezAfj-uKvUQVz>Qt z9_a6QdEI@2$b2Vu`6D~*EB#9L1V6S;`#Z}{pkl6iVEMzpfdOU@=V+g7{Le69(!Q1- z-#_L*JpD)S`r563<9+rvy}NjHZ`M)Jd7vW%PP;EsgqbAET?~&&;HV*KSwQVBA53x7PNm{vCbwxQAj8~rr$4n z&Q&|LTgNAC*6KgC<81t+mU8~5N40mn-?sSDR>`lH<{bC)XY`<%|J>%>_(bbH1_JrRNH?QzvKL2U`eEY0#=5k^e?@PCo9})XT$f zy%?9sq}yt`WktB3(f+deTmGh-zwIl+3!bjM`}6z6v#CFpy9(WlnfSd=YA1)lLCxMD z%1yl0=?DKa$e8ZpwR+g7{oV7O_6uIK3_V3YgNm1dS+`=!uf3o8Lc8IrN!r#4Z&Vq+ zugeaf_fV%?z(FIzV2SCwf?QRT`! z#~W+UE0n#e3cZ_txc>I7;u~3JkG@^q;1ks3=I&(6@l~|fyg|pWRjwCKAh1AmrGS{L!)bL`eT zVgF<=Et}=EktL|8V*R|Wv0lFA;XC%!yiGgAe4}jLBiq_mu_y)Gg8PA?2-4{nSHp_ zbno(G$7KpH?c25c%9YhM2g3X6E_`15pP?~A`<=mES9O_>r~Wg%x%ydBgt=#y{Zv~A zg^M-1Hfqn57xk!Jm+z(S?;Xu+)c zhrOpw?FnWuzLU0fi$7;nreWKT{|p{eiZ3s%Jb(Os-?ja0^&10TtXy?+MQ4ZGj!!O$ zW>wnh)_Zq__wRpuq-DPTfp6@8e?PPN&v0LK`NQ+Ktq+@j_Z082k;{2lb~xsSpZX13 zFM%ch*gvdaRAZn2P+#K3gsfXS({4?hTVyPI#HP=x$!ay@T}Ys?Ug@d$6~l@v%;V3{r!I1FD=mVUx3Bx|Gs43zO-;x+MLU0CMtZhkNR@DS10pU*xaUO0qf!{ysU5zEz9VfQB(#LFjo4&{YK3s6j@ZdW3N3OrK>_o0CI_KD5 zX4fMBGT#2V<5XUZzFxO4`1bAH^S?q&psO-!s*` zPqe@4>uKE}?G>AP46?!#ex5k7MmOwQD9eJM{@=47_TT<|#y9!k>YbaP#1{T%VCviZ z_*?14H}@axZ@&5FVOp*wXIiIc^5l~iE9#8?o!ck>qxa$MZJRHz{c>OExMa2W>x>pR zcUd8a#XM^-t53XA{paa#*+1UkkCLSF9b<*5>nC(n7buWk1a%@4O8ow)7)V6AHMF>_b;X|KPO9!|e+ z`Z52l?T1b~{;X-U_P6J)Q@OD2>23L0_xEj{k=pcL;)FtK^m-P#nRYKfL_aoL{89Sh z($;qi4QB^8&bsu<`9FiMtYN#|o28!L&z5|M@LhF&^3G0M-m37H8rh8@GfvtF6~;}> zPnB)maJQ8qeCnT|kAJLU_e{HBk$EG5A^WJ#!pgbtRIQgJPx4<|#oq9Rxv*%H(Z@Nn zUYV->PVag9c1m8~CE4Th_wxU3Sl{)Z;laYoZ6A-f=6%B6>284&U-4EvGw_eoJXImG z;JL{M&p%p6*XZi3=vX?pgKP0h%k|g$8*j1vll+nR@UPtK*|W7J7EVcCoO@D!op$(9 zsf-<$--*i~DvSx6%yj2wWvX9zf7Fwj@Q0VY4SXiv+fjb`mL#`^*1vgM*Dif!bl~=s zsflr0ALMtRPYSyC_uRz;lW*VKyWjI8>%QeVlN~iCN))(e%x;|_IKlARKIy-E|DCUX zT+b899$H;%=NWCcUX%GhgX{H6@@ytkF8&j$uwpte_0rmt%F8vRe_eN9@oL*Pfjytk z328W%r&(~Vp5*zBA^h3-gJ&K0IRAFdm|o_+&2nq|9>)4MgY%028IEUre>=TvhrQIh zxFjFn<+mmJe_Kb$)F0R$e)}KW-fgoF?h?7C6Ls=U?ip2nX`8J_Gve=w27i`6w=e$h z=2DH|hiA90IX}C5_m4B4_c_)?{F7weX4SV*@3OQdQ9c({qYi;mD zeP(5YWcgE;=d>>@*xc{i(8`byKCj00;}g{oM2U{3BA`ch5cTmY;4n zb?36ov&DrnUdvakPyaA&!mixrQwJEtElpb=Y$}Zn553a)D6(tie}8dC{~1`B zcYm%k$h^ROL@KKO@g>WDQ$PKD|4*dlc~`!3tas!&t1@@-FL-?Lw&-VAl~HD54>C0zq^>P z5G{B7kzcveC#1J)&$K*-b*Ih;`!k-F*`^ULIZf0B4(0|Vm~p66>z?r*I>baPMc!{6~DF9o)qTe#%d$zqmdecheb zn--L66cxTWereC+2j*{6>f|#%{>VRKaJF)>lmAV<1&6(JI8Qop{+w*7yyJY~ zUul1&fITsOaXt4w>8Ky^hi|1#PMfiLW1~X9@+9^!yC+ZP7soAF$v2yIXMMd}R4<&BuhZjWy+iVKRFv&B_ftEMdwt1g{ZVq_UrG0|igmk> z?dt!g-l}I-@NT#8tBO^1443yEe)!{HjG?6Ihv#o!Kh%8wk#~C6{Fk5E6;z|7!{-++||86ZKerlwcJU&F!>?} z%lPD1TD?aP<~*1f68F&I;JUKk@>#{N12vYfc(%0HF>gWm{rw+&De&sMVc*{9{R{rL7R`e{?F z*uUUio?jk`rl{|BbBy`j6K3D`pCQqgDSD~-tZysJ#VY$O9(UV+(2@GJ|DpRqU*?C; zCT*)So5?pf$HU0r=aQNG_q?l2{`$u-ZuYaI>5MLnB79q)*IEB)mYbZfq{#E*faL3V z&5GTR_1afvPr4QRdRoD$wHp>rZ>TlCP*=Kr(e1xGl%3nc4u%C!a9?5i@}A^}m$eP+ z)g{-5UD~o`GVF5S(f~u~be_Qii9r8ow)GRi=pXtTJ<7R2= z8adHTi>ZPCrO)jj^-)jtOe3aD++V&xbhh3?l`~~}7hi`y>2v&SR{3?=Qk$x)t4mJ4 zsOS3I@pozs_kRWs{WqT<#@b#!y>sU_@nXKGORdk!N-s)9*{y?s=Z8{M{8T zzqF?Dw^GGE>-o3jy{>Gn{2p`m+wR_XMR94J=bw3+et&xX$XfnGane^x_oh9Ge)%mc zrR>$&^IVH}3pp@fxqe(u;qQ8z{Ph*HK7IC?b?dxO`O$r|zI{BialwO>4JVVY%=u~) ze(RQgqwZIIzRXFl0vt>&b(5>t>)Eb;HkI{*HP7V8U-J)Dlo_^(D9bvmpKDY3k^7Jw z`^T;48f%No@*bo{F5TXm(q(QNGWWof!g&^dwC``xXF8e9WVPsoSCd2$$D3>~f1bM! z-gYovW(ZAupuKw8)2eSr6SHTnZ?<7qJ1657!uep~6ES7humjW$XzAv^_yYu0PdlPjgzYc$((s!mFKkN0os(E+q`pH#q}wkS7+A! z&b;?eSl-p}C*ImC_scPL$(f(W1g@KGbMohnZjWr}Yt{`&RH zI==f}x(6+OaoyPP;p4=H0LQP1VKvSV&u_|jYf%to#?n(}6Rxn=_EpHi@9XwIEN@l& zIKRKfe&OxjPx1GTve|a?%s*}VyD>LaZhm5Ru25^6#DM^74GGa_bqX_FAAOxrxR>dl z%XMeL`vQsAHt+Gx$lKBx&a9Ab>tMw{k|9y4hgIc-Qu0M`0`Vk#=D_yF0jd0@$>6sG!2V}xut4>?a z^Ll40tLJ+8zsv12mi`D{*sJX_QSGDOLcxp(0oOV1%4ZY*K0bHxjelO9@JIEAn#iR; z-2FZtTbXtLX4KIu6P*i<-%ScXnDoZ#;l3Q<>zRM*UF=iiI}B@Fei$!`@p<@F*0z50 zyMwRy88B`9wJ`YS^|f9fq7QvK@mfkj=iG7d_@!aI(4X+X%lCwTtUktk;6C@Y>}>6s zyt`r#l=+wUyes<8@adESquq`t^RC{hvH#muqq#V0?e*ULrytEW{AaM)xMVHwrj4%{ zB+~ygRCb(Sn)_q_w}c;VNaeiaEK~Criaz_#z*)y_elYB6 zY4lr{tg0&q->%(v#_Pz0^p&!AH}Eg|BKhQcOU9@3AEh79@A~6dwALm{$=a>=WI+4r z;2*5TUro*5YM1?f z5;H%Qf{6{{OUrK6FkL$$Q*`z4AK$Bgx(ySB>psOr%T48PDp|kr{gRapK>HuPf|3eg(5LG+&x=`c-kb?iY`@9cMR9;$ND1 zIEg{B)$zFFEYrD~`6byq?9LT^lqr3Ej{g~J#qxy8qqeNIp8pwGzJG|WnEd$cvIQ64 zZ@>6<$?h~|1xsn+kEexyguj~m^V8#epIp(Wa+W=ZSKPeA)4<31C%f8fPE7PVe~DLG zPd3ypy5#Y3p7yCy3)X2%b#m;>p7Si|46$ z-!&%x9P8h5b4vDq?^;?ryY{23jqcKW_wVkXo|!J}tDPda3WS!-v0+#L7edA}Xct6OTbS7a{pju3iuNFs0X zAsd@T8tl88_V-q-e`EUbkUr0k+W{Z$p4qKiy29B>;)9;@$-u9vI`)k5p?^w#v_JVH z{c-ZmQ(@148-=p^CmGydX!=S2jqhVwv%`$QMO*JoJ881nb7#{=Nd=j-)iuh0mzwVH z`e);-^xJ3S*3AZ<#sh&q{xoFw5LpcK=OFg?TsG7p&j1-ZVmCTlMr=8gDMz9NSUO zWEzuRm^b5i)o1?Z{ac>Re(c}9T~4gue^Rd9D`&as^<0x;R!usqG_T`^irisI(XX;s z?-WGsInpSS+54s_H=)1{Gj#Y{=>wOh3K9&xxWw1wn;obqaoKu@bBf&=&n4@;J&S|zWtL* zbt;>7+;X+&;^S?0ejcwlOq_F+CwjCO$~^dD`Z>I3%i$$I76)&=C8Bb43g7JLXYvb0 zXL6i8DQm0!HToO>4~sgf8k0>|*1!F;H#x@tt>o1(?cePB{s=#(>pHXbf2Um+GH4UuX2jl8xM%KRNz3E1 ztFxYev--&6aaO*gMhF9dwcLBkUXou^aQerW^-KPV|GTtL_#66G@4MnT zw%98Ds;?1OW1f#d|`jHYKj?S!>9M_ZFYw|U&k%renIND z@VD~6GuG7c9Qj^uuDQBm)jNB=M_&ax)-JM{@rZq1;b}QP!v?GG`vpx82$q~qvu3}4 zJJd#aamSTAC-}FPSCuF~pYTQYb>rS$|1N#ZRCpIr@i%+N<%=Cp3cF5T-}10h_U+<= zP0PY*@MgT0(cfjfebKuW|1&VzYu)@i*^bBaG@$f`j4`|KK6IGg_UwBi7lHP>CM%-uUDxl8m~tcjO= zd9w0h_1?v*j!Ju`AChX^w%g!2^VTXG_kysqAs^Xh=)a9tekRLXd1TtBh&#I`lzllG zw0o0u_lmgMY%`DElLqf^@XOnK)$Tw1A}!yjBB=0%LqfgP?2sRc56=rFWXnlzxYD%l z>a<69Bd=63xfT91U7YtHZIwza?F=lUF^R@$v3)*^|La{>C0V zB@Z%uu~>IG_J{7$isi2Jy|0zLDCL%HKhN{}SgqqLjyLY6pT76oKbUt^Z^`^a_mqB{ z?iO-gwp_4wUW3P;iC-nxIeYzWHKd~He=fefXnf-F1#t%b>uh_!AF>m^ zWFx)Aaq)|fs;xaXt?W8)s&<=yG<@g(X~_rwL-lMw*kkuxFS)TU_1e1cKc@@dG5+v& zXG0u=ic^}=NDt$eR{xyf)XePo-a&wt~Dwj#7S@GTwij_Wkvg? zRaqKxPx)iIeujTL{G(d--IQHx3Z_jvx#z2vee1sP{|q<%AI8hNbg3${D}Pz(aA4)r zo-LXh%5)SuU$wyIb+p z-|Fk4AB)}oUHhM+H0f7>nk<* zj-Qim7AeK&e7fJUesM|ubpNiR%1ObBe|$@BZ9Uj`A=ke8(rRyxT_29#k}Kl>w0`!o zS-$$gcZ@&n<9{Bv{Ndfe{IwzPSRQQ;f8Mh`@O{j^oew{1=LepAA9MHERF#C4zE`J} zyqMYd;=|!<>!LNb%kQ2#}()AElHPZhGK(``zcb%5@df zYE#d&a6CTGz_mO%v$&+bTv+f`_4UMs6<%&ymy7Cm$8CJLp7(ev$23>21qEBP`t}>!>5UmfrSO)H(7{{L-%V92xf$xBd1xP`Um>Re1KY$l~W7b~D$p zKf1Q*{_aiP&no>L!}o|U|J!#>qP}Cr+0xJanof^a-O4CaC-i`4GPzvnLb(U_mOYuk~{iStJAFyoT~56R4(|{BU62O_H~&+kz>M*CrqU$ zK1(R>Yn%5VMlol;VWmx}+}8g$e{}!Oef3ZLW8Kch(HT-DA0uK<=1lfBnLR^Wa1!IO ztnG*FZv{vHSpWFQCH*a@rK62mCL7<*zyIkm^RwjFQTD?7G;5SDeCdAq_U#sb;p}pb z+hNzvs-~5(Kda<=!1Kw^bG_2v{x8w9_shJRHS5r!OSjH0%G~y+@$U4j=hwOXFD=zL zdr$Gb?zzhkTC!7rJN$9DxbKTOcd4Y@p@RC!Qb+#I`BTn*u)ZtmdHWx?W9N>&a!xln zf9Rk|UYT|MrXok%P5h2g{~0zf{?Cx{@8Uj#nGN?&mmRjLKV7=s>F*NzbpIa_MZNWh z|AojYhjN44yeLlU_uj;`@mRiR38|~BUZ+0DjaK^v9xMT56BAUUCXAH`_Y3Z@KR_fi;DVhq{h@TzY(NWonj{B%|_m(Q7X+^v`p?P`-}s z+w0%nN0&73Onm%x;ms|*3R`yGiafOJj(*^FiS_LZ^L z`C1dpV8M9s$l}kE{}~#?ZSxPWTIV2>v~$+B<>vZ2vr?btYfIOCSax2b@R@?NKTCbd z*6L|}2>oLP}fYInOY%$ZXhy;`?#ztWqqOL~tx z`)%Sa@pShl`47sU)~9`rE_0sx>-#$GN6&iyu6^j4XC(LN z?6uV6vCG^4^3`*R?=49VRhQ+dziIs7$b6|f{g+lg7dK~oYkzxdGUM?HEj2s8rXBjH zwm;6~i_4B3f-5|(EZwEm!?1Y6`Z@Lw=ErjeKYV`hq>XPS|Mb;Adj2k%E^idA=i>c# zUi3%NFL}M6UVilRH}Xoc?_9lHS}j|m$nDtA%f5HdOtLtVc{J(Ac|Y&-+;`tfYH^BQ zcy*o4+HuR@_g-stlwKbe&!{|Me`;G_Pf=gbhx;5^Y`5MVp2K(b#YfqL(WZ}$ebuI~ zd?)!cp8fOs;1BOsY`?auoT<<`X3g2GZaIa^tbbRh)Er*8-eS@{Z|UhleD`LokBOgW zwAX!aan z4}8HXdHlz9*PKOn>fVI~zpxZ}nL5+)rsBy}LD|9Pq8n!t(Nx$j?Ym>YMJ4z14YJzt z?mz0(O4Ho(>bqv`zN0hk_{1NJAMES?sB~1vv^s4b%LH%vDNiM(Cr&s#@!-L={~6lm zZx);Q;iq1-d*3^YcV07vDrdQ#ILE=h@M!h9nGbiE9AQzK{AG1`)byJHjg4>pXPr8- zBKFQ_-tq_Araqc0R;wR*dEphEGbWbr-&}j+ zDK=}4>$fUKrBCix()OLZQWNq)^V9^}!kBGqKN?wE+$>Oe`@M4Iw^@;|+FsuIv7X#U z$XegxBb#6Ar6{<|RWO8FOMA~={CmfY{@{DdYjqb~(OrA$9_J7L2lp9XZ=dyUw&7_UD9A8u_T;~}zV`E0_TK$ zA1_^-6dUw*>4a%X1>G+L?*6@!EpagVgbHW3 z-@Myzr7@qaYreEQ*68$nE5|Qqqrz@vhXr2G7k9nzB&lrMtp_#2jC*#^dfDc^tvGz2 zZnp04-5UGSr+g8uK3G+M<85O5kFK6;g?CR}yB213+heN0uTWoC<~s{tPYh3!3u-+Y zx+CW9s{ah7?w^;ZosLp|s(+jR;hXYZL9aF*ii%eId}YNyv7hn(8B*V#^&SfmsW3IXR%1LN1b^>_PqBi1^3?A`0!xo@4uH{WNqe2 lF_(C>q{!wBU*Uzzceq=0CqL6y?^}Fr*{7@*QaS%`0swF`#cluq diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg deleted file mode 100644 index c7f45385346d7d6af5ca648ecf9813e176a6dc6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34266 zcmex=o+2Ft9NQF)}kSGBAL# z6eBB`4r5?okcP737#J8dplX;H7#Iv0nHYE(7#PwR7#NHgnHWSE7#KOJ3($>V1%$i z0*p}h0R{#JCProkW(Ed^UknTk%uu(mfn1No7KO4w>KK_2<}g9o-xL|ZfP(=9Q9=jg zXhsHx|F;>O893P3*x6V)*xA`RIXSp^M0k0)xp^dog!x5erR3#grDS9jRP@vplysD3 zWHih*bqowmOibj}Ep07~Z1jvxj6jAka&mI=aPvs;@=6#f$|xF<4E`Tr5aeJuz{tSN zsKme|$jB_n`2PrlJOcwGD=01*!C?jR4I>jX3o9Et2PYTz|04`r1wbLi%*4XX%F4pR zz`$6`$i&RRBFHMFXz0i$9GJ+iR48K9IB_9|veU+cqCpows2C>|HF0u@iAzXIsj8`K zXlj|5nweWzS~We&gn?hmRgV zdHU@6i$mSee*Oaa3*=9TzhFK?^Oqn46C)D~3o{El$X|?1)g~6CuMhs_{iNW3#f~^9OqCkch%jSUDK-YFyGaw zmCDCn_-pX-Z+w$=^kBt*hD!Oot0KjYHC%>VM-m$s9htUZg0a5stDCQnrXHx-&f(`Z zM>uM4fvXtzblq03x{oW?9+zA`e^17Sn7q0yiR!@nCa;{7Ou4(}wpX?8G&x!%7PnQ< z!Cf@9T~zST3ehubBew3FUTP&{Q)tYe#F`SFQMT#Uq0gOhVZD(zm*q@yS6`b|(cAT- zJV1HPv3rPRLQ}y#2j{@9mD~ zwtH7Eyr7%9?c&+^QcUmCe%hO3;$hKtUD^YfWj zxqdh7+jB+uRN9<*S92L6HhUf38RgEs|I_jlr?b;ycPJKzK9Z}w82>PMa=@#}m6zSK z_B0jU_bOxYUfQIrclg}=~xXPfSPj1AF|3>SSa7GAkm%im(%uVV>LO;ueIy)%r$ zms<4jS1JEYetm7$C&qiT7}%!UBrOhOn`d}$!aCD-QEnco4dJGo&O0uvJvJ!x(K7T; z)3SYKW+}NK+;ERu>lW`RCx0GV|Lj0W(>w8f)eh^QY34NTNl2da<(<9N^sp_}uQ?UE z945(#CK)lZ2+q&xI##$o>a^~ym)F9DEx7K*oLSO%SyJvx+<~}eukD*>RW5K?mUizB zAD`jz?V_Te__w~Se3jSvXK~fJ<|E&i#jCyc?z$B7K=&~6S zEC#oyrQS535!-R#4V!^4*TI##cbuL*yL6aY$b7cOe}-k*ufJNz&&}~Uq@wb5l~q*d z^4b6xDRfY9<2xe^A_Oo1S>yvewJZt=XIVF?l zT)7wUvTLe{#_`!f3j0jzO~9> zw_Ej<$z}q7HF>%;#ctj6l=$SSa#hzd?$yg>g;)MfWxaA%!@KmkZFbr%!^{ke=OWh{ zRNu_e(Z0Vn>h_bH$+uQUEsnWc*}hSa$DMZZtwp4BFcD@)!Lxw9eKyD=T0iK z?Z38p*<4SdLk}nJ};8`m2rRV;0hJxc_? z8S1hwwWStVE(q6CIJsQn^JGu{wXXF=x?6tF+}y9HmVZJj!{H*^ij>BUpPudLZ=ZGa z?xC#UEUT7D2f5UEE~V%dd7k_ga;v}h-B;_6%YG!?@mK#CWAK1K@#VV=!5gaU+~Q^> zIaHWVtt?&n(oTHW?mfG@s&72f|FlfdfBD{`sPf)Fo|QT2{m#)l8gIp^CZ2ydD~j<2 zf4<1YpGC(eDgF~@=)bmFzrEyY(#7bXa{IiuB>Z#Rc~<`Du5}An9n92g*4>j=@M+r{ zjpM%;t*DrNtm^LiPI>oBk6Ld(<={%~RJPlaA)fK>?&9sO3EM;aOr%5I8MvBE^&Xhn zZGBe!y3U4OH)(%M@}F$C$48>}wm0AV^)k%*<5kH5_Op%0FLf`t^2IanacfzV7URm) zex*H4*W*8yEzJIQLMJSOuP(*sxoq`y*N5_g>o#j{-r>yqsLAPhyX?mowJTTK3S?Dj z#VH5s9^gK>YT^Nb;#;d@PM_qyv}ASf&K)OhK86aGo7HAYtE*hOu#nkSQ>5F&VA1M0 zA0hVS8)-|71Vh#{&g4n>`%Xhj;gX`!?ikNI*N#l|EIJtS&^>Bd`u2^Dp?wT1bGM32 zH5T(&#x}>0yW)e^P9^W&e%Zpyl|T1kC3Df>Kr$h2}xExTjkYKBDzXAZBmx9 z%&eW6YqT~YmCp`YLBKKNG z$eL9S%zINRCWZYwvai=M>7xKs&tylP&nNOsSuZZ1W?sKx%c@BY6*_EgwL8~GeMtQ_ zWtWKm@^zOEaCZKF`uoXs!H3+TlO1+%-`VqaT9BkfvU0|n6{$Nv#O-a0^0wUdpJB_+ ze@T-*OTM~iaAjhRdi}pktbb0dGd{G>WWM92edkJ6rf)Yg?wr{1>#B>7?uO>s z=p55zcGp(BCVQ;-ucnRaV{Th*r|nxNy%fA&n3`Ygf5JU;@sCf}qxSWk(vI%9+h|#2 zU|_fPd7bJtwz(grvLCg%wqBXa&hn=rJe9wD?N%|vsIz&O1pH547d?2;#OROXj#;lf zri$K33o`p)cyIEBDCzhQn{{PACtLhyXk6?6X!o*xAClsV|7Gp%^b2>)-*Z3-# zT>YnF`)yuJ)Uju72?iX;jDO6!`r-NEvtplJx9s1Y-8LzO@AMw6&p*<3we27Tr5m$-|d-EZd+_sdVZqSG~dUU zwyIBEugbCb!=hijy8c>iZ;l+DdO&@j*0Plr(-=R$%eg9Ck|n94XSwEDx3i_!6Zf?y zm*!_>E@*VEN^jhAdM)c>znYyKrq4p}*BD*-cYfvBjOS0MIy1hr|66i-vy9(WA&)1A zpRIj!-hnsQKT-A&dw@}`)`HC`)_&R9jdACW$|Xb;zKj)^bnn*vLIwtH_41q7O>Gz7 zzBga1`1rL($*OH@)a_!tj~9n7+C1%*&CeY7bz0v$-+k%ly{Xg9b+*#1W=7}Zz9ql8 z4sH3+-cjQ!b@!E3`^}`nDILudtZSMX0`9hC{%1%&$FFtxwaY(wal_VmA``3g0e*Ot8Aza?5{;i;)lHyLEU|FG-Ow^z}f>AS+Fp5AsX zCT0H8Dv4e}ZyDCW$uZ|!{>0jM`$jAO2@( z)c7~`;{6v^A-__$&V7=rx_5(I)O6(q$1T3CE}yleK=X8QT=?U7nJlqgQ_8;O%bnLR zei!q}UnED|x_WKey;FB}bspaq@!lFD#~^z4&i#`GPo~*>E&O)Ws_O2!2l~@w&p(_T zvuyghxGXufR+gS^pH38tOB~}>zmcBWo>j0-|9j_Jj@-x@%m-HcrwJA@nnbLg;h*=h zr|rZt?Fp7^!y5bZw6;t;Fpo{k%hg#^Sl9DxFXUsToKI+1@LsMl}-bve|vElHZ2Rk2x z?VBoh^xj?Hd^(0jI`N8CS8l^yEpafR8hB2C)0uoj6*tYE+4aMKK_VT z>(@i>+j>_E*x9pF`j3RKzxk@*p3|fw4=)}IZG6!6;A^j7zHZ#D6g9IK?xD6F>-=u{ zi&pshgw?+cYxuKY?Snnvq8a_gJXVuKzw$cqS{=&X-F}nDE3&`-KSOM#ZqGcukD*U= zi&dtDeKbgT>cwiepj2(^o>;xr>t2QZGF>A)yF|fctz_uL{|tNlefU2v;CZWdDPOi? zS*+71Bb@~0qR9v356%4R|2Q}^XP?NAmTxU@x6a+O^~;Sr>z@>8-Kw$QyJcpy`}L)* zCw^XOS};Fm<-eqjemRh=d(#?ay*1t%Z?BY`|Ge}W`^xO;@+lYepPu8o zz4Nd3SsTU`+|{+7?Pa}={~7kJb`JgMc-4kc=lA}K4-!xQGgL2JCx7_X-)WPRRf3m1 zbDvyzV?j>>UVD*xWpOFX+n&$=8JI+(9`8J^x3F^cjmV5`CwyAos>ttu`GarS z)-7`-OWw;nrp(M&E%IM^`{a6ui+@D(m;C7Z9OSUioBvD6zlZkK{~6YPT_d?j>_3B; z(s|GD{-gXqf}$5#t289Pdo+JbGLLasSI)~z3pp;?u_`Xwd_*EKY_43_rJ6Ga+F6%Q z2IM84)mvQ6H#gbz%<-JUmC0^LmCxBJ#T{Jm;y*)rmxI1uT?q4vxMzDN`_4{15I6H> z*dEVy=O4)?WxxEyETfeEZhc}T!_bTzB z*9Xr_u502{p3LHYep!aD@q&$4xV@Sn$4OiciDCR75SGjZX1`LUYy0BZoGIGgSX=2S>LYB?%Vn# z&baoU+D^OEe_B(;Dz{{v3VNDuQ*}A>*kn=nh_Jr4d1*V7=51F0yyn;K4^!&$yL80m zKV7z;ZZey7=S#Wl$X89SZ=14rUCs-uXkK|jX~)_iL(yB?leFCrfBzSIeA3xZ^QNvi zcRN9QR_k?Hx2%e~ln2iyxhk&>)LHUTxz9B{=H;maQy*Nr(IR|a$`FXuVlX%{h+dYI~>&%oN7@M_z3^ACH(3py3+R#izo zH@7(e&z zzcb&uE?T>L?pgVytTT437u&dBUYNgP_2eIWdaa~89(w2{?(UO*UC9<@;KygbaOI@$ z;d?*IEnn_jnw>Xop`5|Dr>9p}zmw$^duLQ@eRx*bboIH}sXRe~y{Gm(T5~(|R&BF! zRFpHf!9l|XlFMH1>b{dD{IdMxlJE~J>hI23(ox@8eBwLXR^A2YdHoJ)nk*>n(^@la z+T@t-X{|!n5D@kkj%x^SwT=7k@1jA02z3UC>=RUG^Mr-mCBHc{d)ed3#`;-7}$I zr6<3-v42`IG0>)AmRFQd2#52fExB8zS4?4<+5R~4tBOxirs3MNORs)qd;B(tf8kNJ zpJ}<5b{l?8p0!-?W!RN#L6fZ#Cce7l-IsmZCiXvrGSBlXnt!z~Jl!F;_4zzq>#ejYs!U1?9HjgX=bjwsS;Zp7lIGLBpl{$F)V)w)}z7t5aDwJZ{oXIXv~; z<5{2AYJIuwBO^cM>$?98{(HPHb%;aW8t!ZvoE`zmBx!^M5QgZopR0CQ^lPp zK+m}6*Y69;z3m=*BJ#wtU-w@3i7IGxD5_g*s(kbQT;bn3ck+5=OmYr9VJKo;*IJYM zaHk5l+T3EP{|ptm_SbgB+MRZ}y0-3es?uqB=jK!MovZ`c=Vf2p{OUSqxo}6GftcTp z0;}{pp+8OMCfSzx`d#xbsZ@xFuo7dJeZ6hRhktWf^i4MD6s|aJEW@BZVed!&wzX-R zTrsi9KPE8jpJ~Ejve!Fc`&G3o>(Yhqo?2)wa%;unO%^tf_dNGb zC{H+<@#I0}gIxQLbq6x96|T@axm{9X;l`;8ujVZcitazOI_A~o#(slKuY}#1-gGwF zU7eqwse8GpxY;9V&G+*BErz$Fwq1)0l2l15Fg_c*u_dQ>I|L{H~+tu^4>&@;o zS;~ZmE42kibj#gdsbqW3VXaHiq$Afp$&{@1)eoI?^GchA?5eB(=0&Tzrfglkxa8n6 z_QjdEwuBcpEuZ&w;SSTv-n~b@U1IB*aCmF!udsEOk9M|QmtQN*ab-v629K9<{^{~F zKPEjfYGb_JeE!4rwK}gAUh!78x2r^-?==6;YQbyF_U)YJZF0EO=z%b_4QI(X0 zrp3xH2W!%Azwr+;%Iuv~*|b!JU(}_`ioY{+jH>ykOhr>P|+5>%OT8 ziVhx3Ya(wf;hZLO#cJ7N(K#ybo^bhOik9|zX>0KDpS=+-x$~V`=KN`D^NWks7M7&c z@4gYb+B;cg)q(;|k<3i7g(0WzuH0<@$4h^m|3~EwzY?zg-V^!KPrNO0OP#5ESkK{y z-di0%*UoK_`?2I(virHXDM1rGBNuXB+w$c?a(LeJ=6BkbY%yV5H$7sg^)(QBB;Z;4 zpkAwV%6tp|_%iZj%q5}CqEA+BXD>;c_&o5wz1O-+#yyNjtz{-O zN-Phqk}6nbF0@yd$&Be??Z8Pu+twwCXA?A@bTEf;0- zCLiG!d3yQL>=mM&+0{=@1op8fN6Oh;m%HrR#v!vh#=A|cplrSR*Q2_f>4|1Dd6&QE zN&R>t!0MXpxAoc{Ois$&H@L2SIJRK&3#nvJ%ZXT6_&PoiSx3^)1{h%MN{_qf0DiW!WZpY zu{%pw{W<&nN&cRz^^P8<5mMEjH8-Yio5ykRm;Bnizl{eTSuq_qU6=78dsh9;cXpBD zGoCU}v40p76DR*MZ<3FhxauPH-bsZ&<)*$`?mhFTGDC4F@4xm`#ptzuHzP^|e)qq0 zkO`amKsP*mzxUj#C!6?I$ZR&>R?YQwuDyTTp}WexqM;(J0R{|f{u&>N;(c=H-mzbr zYHyZ2n7^#*T7I`px_JAOZ4C~ePZxOv^spKB$FCJ<>AmY6(sxaH8pCzY`g+wb%hjKp z_d4`ZZGUZjnaOJ*-@`q-nZL89M!c|7+PHUnMT*dpKGA(iA=MMAtFOE|b>r=tx2HEe zURrXibV6_W#FHy@bhb^ESi9^tmrwR+mNoC6?YC|@yN}GGK}V!# z-^`QVdEi&*?{(>|wx7Khdi;ERG%EdDct_p>X2H#sosECgXB}DZ`(yRex6^L@61PMvGn_N7&lGki%TiWrovwzgq z9Zs_ER=%}$(%Jp(3L&pE72bEP7Fkpz9$0DaH@9)c(d_JNYgx|}nHeg5{1Fz^6)jM1 z&-LZ~_KI_y9H*ied)4h_|Gd8H3i~ge?pGY^&3_m#w%t4T+G?%G=MTC6dto#G{Lk>E zvAhS?Z;c6DyT#~De2GF;$aKS>)6UthTHAS3`S^=$58eaK_f`rOmY1bY+4Pld$9u5@ zzI&H^XV??=srU7(*XI7TwU{Jd*LRJrpnoE zoIX6wk^8hF-qu7fW__G?=JDTOvoqAHO~NON-ic;jc|PlNY<9@*kN0-%a+_`7&2eMW z^M%L5?JtDH8C;L|zbGg1L@9dCu?2F4cdlzc^p|jbxMLZA+NG0*k`pZ0nHM|>-rM-D z%%1Pv(~Exss~5#i{Cy~-e&3S+42HA5S}yP6NSPJJ)vQf9Rzcfr!=1S{w)0S`ph5fN8)dO7cVnP4%Ad)-{%)Eq$CqE>rYj{&?WH$N ze)d~;=R}cTU;k}wuPuIL&6{`ae881uX;ZH}OjAkNX1T7@?|sJu{puW3_3|B6JRf+w z?%lq7|LkXfm8TKEc$oXI?f!Ae!hvhC>9erwmw)gd5f{6B>{3LG*~9*K-<0o^n3hL= zT)DqjXN$|+cWMfijUM){vS;}hN!bRSwYXQSHE(m_HhE3|Gk-IGIp00H*r8M{xM*X) z$%@D2OVgRIUw?YSy4d-R>7D({4?cBRedn|D++Th6ySC<;-dgg7Ayhi-eyPd49}9bR zdFmxInL{t+Jv@GXdr@t5`1!u2604awEEK-2U#Yi9==hc|iCsUxADb0;d5^o+s)uQR zAN}=w{&hu0(T#JP`?J*dn=W{)T<|ALLYYIPoN0|lngz4v+t)!VuJym%*>2w(gss zV_$9D%DzGN{KMGZThhBd6_N~Ixn(c>Vg2w?+xwWEKDlQMn(W^%{?p1EGcWqYm$!0< zWQ|T~YnL7gSkYV07|!B)yYPj-$Z6;A68{+l)z<9(_cwUMYQ5w7cDbeD!3Wf~2E1eF zI`>gX;w$gt9V@r|ZZuK0&D`YiRlD|p#oNf%qMdOP;a)FPe56jzIt1Y3X1P-_v_nuWW@zVR|bYtd<<*T%9y#tKdt!F`Hqi!PIX{m#M9DzKF1X|Pcu0R zS@Tak#NhZgbdTY~-grMx-FH3td37l_j!druqKjnSOY5(!nx5)Jq*|iwftUenh#u{jJ z#cbZgw90+m59hi)4vP(sN&77FF5<@1?y~a>Ns+uLIC%r^7K zI_y`jP3@g@pt0juNYvC{hn88KOR@D|D!$=u(Y%!<9KTK68?SWLw(g%Q_Q=xI?UHbz zo&Q~#$*-d?9N*FWN2w){_s*hwNBfJkmd%#@21;%7MJ+$wmZK5b=Q~nr~W&SrdBka)%&u%_c%+!e4h)aKH12I zvs_s{_dp+iR(6tgXF{GzZ`X$JA}P%iuY?%sXv;onvzdJT-eP5T*_USy^jxS*d}kju zar6J1{~z*P%qS@-u+rDhE7nU)$xlkvOU}>LuShJ=H`FuGXRxuaC`e4sPAySLN=?tq zvsHS(d%u!GW{Ry+xT&v!Z-H}aMy5wqQEG6NUr2IQcCuxPlD(aRO@&oOZb5EpNuokU zZcbjYRfVk**l?@7Vk?lazLEl1NlCV?QiN}Sf^&XRs)C80iJpP3Yei<6k&>N)O;Jjk zRgjAt)P$mxG+QO8Q_IWC^~#O)@{7{-4J|D#^$m>ljf`}QQqpvbEAvVcD|GXUm0>2h zq!uR^WfqiV=I1GZOiWD5FDy;^F7WlWa>-9F1zFQj$zlloV2ut=#g9auZ8zl`?Y^ z(^K`c3R2UR6hPr+}#4b2Rbj8n`~l2a^`VET*m(@M${i&7oaQ}aq}mE1FP3&0_upaBV9 zO_;*+j8stA85kMs8kp%CSV9BA#0VM)`gRI7`k>4R@ryn<6C!yNEQgeFVakJC+(1ll z$pI=w>=Z`9Xb6mkz-S1JhQMeDjE2C-3jsv;BsDL^R;gUc-tPY+o(NB8=lFmie>YDb zR|e1uY90@t%)ERi0S1QLypp0IcPE92$S4K&a|}!joD6IXh71ge$;Ab}!EPa-HRPVI z&I-jr?oJ>!0)M;40A6K&S=S>#K>;C76iYCwK$o9fw6^wfgz{7 zqyQxTfq{WRBqVp3=GUZ3=9lyWtpkv3=GUy7#J8-a!PYE85o#7#KKs(vjFnNbHi-3ef%r2Iu^O%A(Blj1mQnWK9J_OG^s{ zkJR#<)RGe2fW+kN#G(`h=ltA)#Joxd28f@*VI>3#TLn*7XEQ@fGc#QyJwvcCgup8a zQ9_W6h>U{9&*wmJoH2@>L5Lx=BgFC-F)&zsWnf^QfDlWX$-uC^m4QLx7D7yA0Rsbn z3j@Qp^GN;?1Em+Fo@r)kvK}Zv;T92809T2dfn=W^$Ub=3DY&GjC6?xtD1cIRa(+&J zX^}#4L1J>Mf-ZU@CR-ztvyj}O6O@{kT9lfXoT?C(nOdHim#*NPpO=ye>We63=HZHA zGPNPO0mOu)YX$}e;U)hVMB?=r1kQ^vFuy&+z#`1ez#KCVw0{R4Z$6x144|ABF8>^o z?m;GiQwErUAej0=duxg_)4}P-IVeOSxwNPZA`i-z46F=X4Ezit43Z3T49W}|47v

y}3>gf$3`Gp(3^fdm3~dbE3=m$mGow!W7Gt&Q!=$#ni&o$25a!5z`u`ZA=H5PBUF)dcgFW z=_@k>GdHssvm&!Dvjwv=vp;hba~g9Ia~*RR^EBo~%01)4f9VHHWpzP z1r}WvYZecdP?lttLY6v~9+p`wD_FL%9AUZ4@{r{N%Rg3LRvA`pRx4Id)(F;g)^gT1 z)@iKEShunsWxdM!l=T}M8=DxLI-5D02U{dt7F#u258GU}^=t>&F0wsl`^wJFF3GOV zZp-e^p2%Lp-o`$YeGU75_Dk$f*?(|wbI5ZTakz0rapZ9{aZKY_#j%g$6326n-<$%R zs+?Aw{+ubCm7M*YOE`CNp67hZ`I}3SOP$M>D}*bHtC4F4*E+7FTz9xWb8~Ymaa(c+ za%XZkaL?f0zgi5b>NNSE#d9sUCDcx z_a5&LK4CsRK2N?BzIwjdeB1ag@x9~c6c#iT^cTz(>=9fgcvA3%5WA3?kgHINP>axFp~FItg_(twg`I_yg`0(! z2p<)GD#9kBF5)SYCDJXjR^*(>2T>tW6VWiy3enl3dqp3JF^j2*d5YzT^^0v1yC(Ka zTvpssJXO3?e4Y3u@$VAS5)Kln5?vDOC9X>Rl2nj%mCTl$D7jtot`v)umQ`d9ivTx+X^DDL+Mizx*o&Q3XeZ9EBMQM-)CN$|!m$mMSh(Jg4|uNnI&asYz+Q(p_aP zWeeprCcW)?@AOslqx2{0AJ_k9U}}(Ku*BfDp^%}MVUyu5!%s$9 zM#)CAjjkB;8oL?S8}BgwXrgVBYBJB{hN-ZruW7sKA=AHR=4M4^>tC%O4&oRGl zAz~3=(PMGKlGW15vfgr!#!kovoZ}oe#J$ySTe_ zxtwzqat(Ez<$BLe$t~S&wc7`GQ}=5310Jj%-X0S@u6xRQCVQ^*{NQEoRqJ)co5wrY zdzSYjA1$9EpIyF8zFxkQeed|G`sMj;_h<0;^q=g1H$Xk0Fkp8eTVO!o?7(M1hCww! z$Ag7~Zl zi&_@-HQF_LYV^|>)0noHYq9FF6|pDdq~dbo_QvzaC&q71U`+^5Se5W6(LZrf;@2e4 zq&Z0+lAV*MC%;Z{NSTuIGSxP9QtILn$vzKT8&k4_2pUaV(kh?QaC@(wjNWNTtMgGMC?Si&~2ZdIJQwu*7c^54!W-N{^ z-cce_Qc!ZHRHL-D^kJD@+3d0(oo41+4-+4rR!9;LHD%oKRwAkr+N*0XY~H>OY1w=Z`MC=0^5YV z3D+jtPh2rca8lKz$CG_0Z=0eprEAKUsqs@!PBWP{e>&In(&-Oo_{`WbQ)T9anSW+w z%(^<;Y4-X#GIKiTe4m>#_tHFvdF$rO&hMW8YeD9M8w=eRZd;_bXxd`7#bt}1EeTt4 ze5u9KmCK}-buat7Ja74f6+tVGtTbP_a+S=g{?$yYOIN>K6SL;RT9>su*6FTWxL$01 z_XdUyr5j#tOxSpBllP`Wn=Ll4+oHN<&Q{^AUE3J9Rc`yRJ!AWW9g#aO?)2JuXqWY_ zt-JMhFW;lMXZBvvz5V;R_OFy-W*In`1nx#q1%Te4qrJEc;wts@1rM< zxg9%v-0}GS6SgPzoU}T*^OVJ@?WfI7Z#!dpX6sqgvs=%Zo!fTa{QQm!mKSziw7IzN zlKrKFmt8I&yW)A}^i{vB7q5k0yKz0{`hy!OH(uP#x%u%{*{$EV8}6{)>AEX$ciKIf zdyDRC-e3Q~?7`lLE)UN<3Vw9^aq{EWPl})XdD`-f_t~`P^3PYkFnY1)rQ6F3ucBW) zeO>VS_nWr2f^X-%(|EV#y~F!6A0j?H{aEzz|EHeMlAo7w3jg)|cljUoKQsPn{@wM@``?}ax&QxzcCs=sGckb>Gcz*_GcyY-2WVp{ zD;GOE8wU>;4-YpNH#aYzkN_{AARjlkfS7=wu!yLrC=b85gqVngkcg-V$Ph+m78X_( zR!&w{P7z*iUJ;VP|3jedupby5n31=`$}li6F)@SsJCGI&0|P4?3p+CtBL~C(BMefg z8%jY9Bqj!C7B<%Z(2bb_%mNGyjJuB>x%c;$>n};>Q!ba;za5|ARlI9={MI{$nX^R& zKc-!ou_HBeQ%aHQhgC9LF6(Yzm9+5BePb_+xM%EUet&j6P0*bi&aFGuT4bWd9GxAk zc4l>fY8EG?OWN5PrOpW1FHAnmkv~s)nXh*CoodfJk{?5k|IXjoEV9PJPscuF^*YHN z&o$3Z3i|kY+O8l!_U*Io`~O-Rjh z%dzl!b!KDG6Lr3{!u8_uSB_3{Q<>S(aMw$@^?6^*^5wi2E-Y=(E!x4YY<0`C=Sz=FE&W3v%b-Sz7sWtGs&qg*U4NgRefC%s<~>d)l!Tx_*1N_s-+btNboL zomcqkiH54GlaZ6NcXcVdtXpB7lM-oSAQQRqa*CwzIg#$7)3Il6te*I6_9nHHntcLy zgrgcJuUmOPS?F@ehn?4^PT1W&T_eK$a^Z8k-|5y>onP(!x`R^Q-&3+^Q&c$;7W!+( zeeHB(kL8!uma|8-l&v@wy}>YKL$sk8d!tR7cT1$H&b&#J0{Fx`ZIz2BdQUbr3%i`k zX|qH^?B=ac@rL=QPu?}Vc)$34bG_fsrt7P}$Y=`v_6X=x$np;63r*hZFUN8~$}4<+ zVA-YlQPP*@Rqy|^dHa$}3$>-zZERZME%!ETQ%tbVpHz;PqgJ`PR&PF;+zXra^3P;9 zF?HJ<%ac8KwNFdm)RL>`ur<53etzx6xBW5EA$QhHEuDNl{jfQ&*QMCSlJhOk>|CIt zsQ;`tN;7Wd0kghcWj940FLpb@qw^^?Y<}9Y@PhN2t8Cs*dAeF7UFDqVG0}I%nKM`L z%w6ZSB~Ezz;%xC(*XN%Ot$MrO|Mu_qyH{^-m4DH;;6~^}zM}`fL{2pxI{tp&ms5f7`;i9<)%teqfpaXQ=hA4U!9Nsp8Hs8 zf?UBK%*19YK_fY zXB`a{oO^Pt)pc)sK06(=>o(`LUU9FJQT?;eJu^I>y-7|Y&1*rHzW>km-r`-)6!y$Z zk%@Y8O)zlV%$T+hJKBCvm+bjAu&UVseBURV2t|dkZ|GiE^2FNX-mI|ai*N93E)Jet za5(Gdq}u}F`ZL~4&+K#R>~)-RsJ~lu?veGGM+?qQZHi%A?8Rru{n7KV^3S64=bmb* zn_oF^{qWbX=Pt#Zi{#g9cT25lQIdGCY`Dxc^Tf70rc-YmOM20~Q*N`A%*(t$g`d;8 zMN7}=WQM~)V>K0%|F;-87#SHDS(#L$ z?=3G{oNicV6k31amEH6|`>(+QHXrxq`MYc^~C=<-scz|6Rd% zHCFz~{$X8V-+X;%?7X7MTiF(LD=X|Nn6u+R%LM^drv{NKk99ixr!>l)`CfN2_fF=c zOTC+oKL?v9Xf^1w zC#p7ma(Uj*?4v7byM2d8^5pgmkI(nLRc7xgyz(vgy_U$kr{N+e_1>x7y?#@{Kj5eG zw>I60iq)$>vh36K`to}-SGlxK(?q8_?UwzW;(6!4*|*)}dY@|c^G(j=lhMnk-fqsk zmY;fmRcQLhTOZfo`decp@${r-avH;wv`tPCd;UB;+jDW_Zi5S< z`N?e`*3CZlSJQi|rFr49WfOmU=r5P!O^kAyw`BH}RZIU!?k-%{^>|P0-NpCjU2nG5XJ%P1E+*k^82d_k_O2V5m7zwG zuLb9=nH?;)`pP5UUy^m;3SO&Jz`W+B(~6YE9ki9r7(NEc;Ku;R{mn>B;$ic_+d% zX6jqDhf9T8Wj)h0jZE|Qyux|g=j7|dW$S)hiAP*9(&#+3Vol4lB2GVTzjWu$NmHMg zw(JQrGgFEBTYblEO0&7hEh%aHH%x`yO@|K5>7lsq^9PR&D>aW_H7@)p+=|dVa6`I4h9P>`F!Va^t>`tjVQD z0bf%B8lsjMg|F^-HL-23K*W#D3pJx&R4e;t-l^&FR#%d{sxGDeIQ?k+G$&QsPs_(8+{uC0RkU3E$G1KVR z(wATJr-pv&D*MLq=h@2#-ufSIyxLgO@Yv(TOVd;z`|JJ*FI0|BoObZ&sg+NfrKQ4L zPsiqc-}_?M>fX#Xt51E^ZeQj8Se8}q9pn4Zk6T0v{KIyx{yU@6E?lW$!6OxSsYzXC zj{|b=%$PLqNxL+M$@5P}S9-ne1PzTJrylrNzSD9-!Fxf)X)MQlCr)kb*H2qIY2vo@ zNG6%-lb;DsIlYDTaZ2Tl4sq>^CD*eSTzB3Y_O|wDY`($kM7wwFkDXsfOu0Vw{#)<2 zCC8l4I+mqw?wom|?{27Kz^AyLsQRht$;U+keOqK23~p7~$y!Q2K6Ye+XXVK#v2q3D zJ2P$Gt(j~(G4)AoxT50I)4jG#hv&3R+G{9ZV{!D@8td!5VOkgOh6kPfc4I=yu9@0j zI%^x21-CfgR8ITgU3*I{s%@e0WUKOZv-M2xUihG@?~}gpiJ(eQ5krfhhf8DDnfE(S zUCuau^w>f34X@aP)tZ852hMVuUaVS}yiuYwWy1>3?ou(;jS+jT^epnkUsilx)VSBm zRlurQ>u1drrvioBms$%~#C$5V^dPb}C`qbKam=ac_{N%@to)epsopV#a2vL(CBm>V+NqIdFTDbfAwcU(9%uD<&tANWS1oZ%ClP)SeT+GAd?t)^Gr&Ds07c2|P^ zCEjg2R>p>?zgT_jL-{({mx@KJJ2vjA)YERh^6_)psq?Mf$#NnQMxPBHE~wnm7cPBo z_BHNR%TzstX75(*)SNZXKQpZ|$TX;I_M_?VW_4Zl-gWdl|LtY%{~7G!9TmJjJ?$xr z3fZ>jrm33mt7WSkUaGdedb3FR@t;32nWc6S#fCp6j{~7Extk>?glPi}p zPc?Ko?dg2<)T^7xx)sZp9{tL-BJa(QEf!~`*O@o;HEeWr?q$g`lzic~ZJFNH@7hP3 zLr(T7O*|K|=u7s(58*ctcLlp$b=lSW^Wbl(ms|cboIfRZWRjk~%%0G`Ij7Ug%2d)~ z)l8$?lw5B9c~x-z=h6N(k97kt3nj$Ma8Eq{SaGe@PMM98bMjpM4AstP2~2b9Suvq( zjY;RG%T`?y-%Gid>EE#Je7x;lN}*Ry*^|?@C)Te7`C#@h&BJdJK08f#9Fob`emqsu zr)krysrse={^nYVRO_Yt9Y1mW;!V|~C$oKSJ?{6*TzGLcfs&We_vZ&Vg;-@tg%-S^tgaVgtS^haZfO6SwP*Y|{*^XT=dw0cSN`5Ug-I%(?rfUxOpTkod1eS0e} z{AVA$MNfJ+f73Wohix$4d?WJ)SL} zy3%^XKjSqvGbWUmH?88yI`Kk%LgAChcbjK5Jqui?(506jHeJb9y(=Vo)yI4@JNB^Z z3h&hqR9Rxy22VcvI9SX+am$}(PIc>(3W164_s*)j=`eoiU!P=s;)8eI$BUcyH*Yr! zTCcVL^uhJ7W9kn3C*OSWz~kAoleY`bwpAJ(N>%uHZmRLbHG2ZoM5?OzJ{oLTY~@_` z=<=2`malskI=niX=-4mzrfu$oM76jS7K`9>$NF9LI8Ur!Rk&U7`Gd+o-aptsi(mW` z61L90>dimRU19taFQ59)@MfptI}`aOuD*%v9;Z{YL{l1tOnT!@J~^|h^!a#4{^XdD z*2KMZp3(XVK`T%5F>hOa`1poR7mJjx%v!VK=g&JEMIK*TuCZmEnNP+>XOCr0$$6RD zbxaTU5Z4RbF^$|ww+zm zrO@hlMxp0>@GJc+mKj1vg}t6gE4S57x*6-Qt#2&9Xi=-tmt#7Ik36iM)_Pxg&ZUcc z{v0^&a8vfh?JlF#n*yx|itZ)E1 z_@C6fGs*3Nv-`cr&8N+K59vytT=6pN=#!cit;Y9?BR3zP-*(~Vv0iiQ3AcF{rSVG2 z+nw4en-j6=qtuj}kw4rXt}uD>?9fxOu#|^iEuKBSvMgogJI8qmy1{Ua#k> zc0TXw<-1ki)!3wD+896E^!U%N znyCggkB+E|noh9GEZHTpBs?$F;#*ZrN~Ng*i{hHhO&b=TRF+=G+Q0lzS3vr|yCf%De{cV;wHa+9g60rsw?ev6Z=y;_ieqsxgt02ebK{L zNB`D#e*0rle=Rg;dS2k%J1%{3ng1Es`}YRSUGKZ9*7WXsU+;y--@8k;hxnOH+P3uM zCW%imS2=@LDB1Q(PB?DmG+FZ0ipB>M7d`DwWQcc zf_fq>jLfXC0VPHT0YN511p`H9N1;F^WrxJVMsN>=nUT?+@g^&`-wF43s>|K^tsYJ5n= z398W=+uPf?LKK=l95B&l^*Lq5CXvW`+J7^Ph@<1%78Vv~XJ@A`D-uiunn4~?5@cXv zVg&W85FQc~FjQbtbPP;15Mnk0dxi<*nH9-_LVuMXDh0>Z2t1fuA*KG{*`q`HZ#W(; zP&jefPBQ(n`eC~XDcOHc-a4CetU36hO)7JdiI=2*dj``txz!q9&N?x%ghgzqa5CZJ zWqBmAux{e52wyIlN5@L$vY#?I+?JJSza;VYZ21exZgCfwue^9G5_|6Q+5XoLXX!*9 z-rd42c`3uFr2CF)OW2Ws#BPB}-AARmZz(9YZ_BgUQ<+p;H+lIIPA3@~!_(69E+32I z)8IENd3sCWp{S|;t-k@E7f=6xguzLGfsvV!m6e&3m5mK_3Lpa`69cm#i;$wBfMcMr zl0u?UVWYA^(8PmD8;hJo7P1;QO?r6IL?yU5dGewUDT@z*LxYil&7SE}>lFk34!c?B zg~S4n%}`irdGY)5{|qJjzTAH|ZMKD3k9&Prf%Tkko@-?;o(gJKyZHEW#V?^{9ebAk zuv`@8#j`Z(PVn-n>X+-n=6~({rtTUkuxho!+Lm`UQI)s9zkgdc)vslhslQV1$6HUf z-c*^S;xUX0`M4@ryKFV0H)m^I<2Wa{;(9`0QW;|^VM6Fu7R`7^1!Q1m~8xJvoD zd!FKL$Ck{@P|ys0FzP;xSx2^KiYu*q8~*p_i>0!CXBIc*h0NEUbz|MvrQz*K?B)Ny{c?Jr z8Ebg`xXZ?uO0y)-2ppoT7z$$a9wn~&}sVORVyC+ z*(KbwrJ^ER*48Wvja=w0^7Vn%|td+S=|S1k6g6aPM& znak3$;zF!wg+hZYX?(}#cgCxWY<8`46%MX=U%zbO@6_W$)!Q#ke8O|u zCe>@7tJ8{ylUP@`?R#77$;UbUS4&`=@_O+y^E2K@_J!I=1o?`{J?**lTTbhE@g4Jw z;E&n6WA5c$ztv?O==gbKLy-A5ivq)C!ZLi1ln1}p)3z|( zVQ78szH`Oy1$*SA;@2KO?DX;8$7>b;8M2o%ymohb%fYZ>Zu8Mw`6|nI&-j?N^^pA; z|GnKNt3G$W6aLmLJF#8JVwLZe#=AOh-41)!Fq9Se-&DPMVReXSfWr!{lMT8BE9Nk8 zml@t#U))sr=48z3m8<%*ce7-4T2IitJo~&?&T`kgvqFMTeGIl`s5)u&bXip5w4DYU z(;XD92xz=NF;(vM%qJDv%xpKz^pk)JtV=Wg$<_1EfO|I^b9&DPqnU{%c7W2?QU zKl{XClCk+{lYj=hTdnQ-t(UVGb4vc=P`M#;up%?uJ^GB^h25uZZ%m#wQ_O?O;igRa zX{PH8@ktj8c7J%V@Lq4^(kB;Yd!2}x(J(QrbCLPtIE6j)`Kq0iCttn2YRxMlQO0j> zJo-tkQ9;5-B9C-ExoCQO`ZgQ(pm$o$>Y-A*cI}$)EVNrAE=tzW(p2lhX6Mk=FYie7 ziKtl|^YqASD~Wf!clGe9r(s3f_UtOB4{GpT&eGIYhz~V#in-=06L@Yu?=G#Kg_ru% zcsBPk&#LVT%d>Vosj<|$-1N+zd>QZN)1P0Syer|W#`)%L#p|*ym3v+;tyy-!btZA0KL5k; zY>2z+A;+`&W>f2*h}K#8luazXmDg`sxAG(7vQx8Af%W-oJal)c*9^FdVi@HXN0jm_pl^B?(7 z$lMSy+2quu?N1nH@vaTJJz-00LQA=Sx0-a+rTM+0h5Eh6oX%c8?IqSV-;!g;dNm)v zCGkrO>MzZ|pS`8yN6mwvt+$@}y{tVOKZ7f1>duev%Szu(n%}(X8~6TxuN*-cJ|p?E zJ*L_Rrf-fqZ4~$@e)c>~n=A>lj(1BQaG1&l&3&!+?C|Bu2cNz0o5DC_EyupDYke-x zk{%NRBc}gi|K9y?_CMFz8m%_WosKDvLP_k_i@hhi%?zF^ruC!Xn6mq-iOVcsp1x;k za^=dM^^Y%}S|tCmYpF^3%3b`E`Yxxrru^A; zZE`_<+wz~T%l2HWte^j>cv)p~L{Md5)T}pU`Fr%|%?P*3seSPKy!q?}`sxK~C7V}Q z`JcaV^v3@3(CII~Z?&?M+GSkrePYXrEpbsrD^5;W7`UVT@WW5c(d(Xc?wfnGBd>Bn zcIj0EwSD|b-}dDiliW=s3ScfYN;A?$Z_L(9*Z`wvgfZ$5ibZTaGs z$!*u&rOc-%$##7&>FN^PyvXt9)#U#SI##b66SZx{>{+Yl_D`sr_iXjVW!4@tt38(1 z#NQ36xpO1-&a!Tiv^K}el)O8av$k;`j98?1_UTbvF6++qGSGM!b0JDxaZU47 z_qv%GLB{2Md22OqOn9^MuH?@zs*mUWYvo&QqxD{qL2S8S_v&xEnqzfNd`(vW_>b?H z{Uz^}7VKOTC(ioMFxhO?)5E?ptg@#&mv44i@_oui_lmz;Cw;o`xO-0o-?~Wi=@NIv z_pg@uzNgeytR!fK=f3sI@qMjUPnVwlTDkedu?dfQ)Z$!NzDF%=kXRJPGUv3}&ZAdX z1aexleVZNP^kpJng4xn;(*>f3tXD}coitU;`Blr7rJM_Hm?>C1axY|RcJ5wz+H2Y} zDP3p%%eL$>wR_7}XYFhGeW!W5_qlf0$jL@LFAp4#iJy7EE+Ot($~5cK)>%InhrbuE zl3)n?+<9ePo9LV~)iwR|DnrZ}n0N9dnY_JMqO&x3fxLLzijJJ)o|nB=GzC9?xn|>& zrD`8PZG9PjyfSGH7OhHO*{(NaHD-lA zoawQGzxuTQJ6*$LwNa1$GlY75a4diGskL#&Eh(K1M`r4)q?K(<>zHixdD-zerTky} zkFI_%DsyLrwTbG=)M?W*`rq7JacOVq-L>;ro^5ToG;spoF0q)2*Y`}C{=H+vtn=#T zJ8wSf_!RVO^2fY_o2lnJCdB2pa;{zF*nYr#$JPtPe_}9drTPtP#_}|%2 zy{ZwoyCQA<6WP~UXP4zJ+TyM0!z7v$Q`xSwdUB`XQ{Ou)-iEa%DV~1v)@R+@6}HuahEv{f}qF-qXXjSuxR99#odUzH0>3RtE1eBIx3 z_MZi!uB{Pj40?YnOkk#Xs8FWk&eJ?a?jhYL7@dPS04q_wdti zd*4+Av!p1xnyTy*dh8VFR2lzF;7#bmA3NWC>vPe#qh84O)xoWNn`xv=%CW^B(`?`E zW@|7}-|new?JDVcCLyY1y_>(fQgw+-x3Aj#bMm~i!lPcQu4Q%dG-i2lY6a7#)kZZL z_G+_@{EU)nB<6P?+EXB&-F)bx{DvJTmG(YatD}ESBq$;(i#4nDeO;CRZqIGsd{Z^s zE*tF)3IAAh(l2Du)Uv~0YwpXPcg(nDTc+^PLvUyIqS&X}-B|&Prf*a(s4>}n^y-g{ zE$>qrPwJb0zP>&F(YN1m*IIuaJSr%cYV)bWG+6JodUEh`Us;dUpRc8O-deI@N+72M zUeUqJy^8tj!`9I|a(be5YM3wOiAC??1ziElbo(lEoBTVjQyP87wy1 z92hot&99@64)APRTd^Rir*!pl&+uDrI=8|!br;Y6R%TE>>&3b?^H`sKetGZpgbMcV zm8F)urdK?B72$B}z_oVe#v+$BV$FVi*Z&`3P#0hX9emA#SPsC*AgE~Q7?@btC@^v1 z!4DfRDm?rMZQe85D>^vsQuQc0rrdh`Z_wk*A;RYu?5*V}Ha>oNn|o1Pw%;x9($Ag? z`{JS&t;+etI_*^6=gn8nt}G0juz*kM=*(b)nXYRV8GV_>C{-r(J}S&gze$$S;h-tE z>&L?^uG9A@rkL)?y!b3Zvd&#-**`VzOWA$wHzXdV9b03`T8%`b(owhnZv}Fljh1qGDBiq6&Tdy3{Ss5ysy4_gL zDeR2gLyN`PTSjzG6tC~|9cP~2KE7ZT!;qLAq`hp)6 zmoQ8hTXMGBsAnnbb%vTW9jEk5l9vj+Uh3Tn)|z$ntW~yZpHRs?#Z&7A*4^w`C^Iz3BQ&I%Vl=m5Pr0a~k%%7Q7e2^hc-B z@P@_58Qa$8{b%?jKO9At!pC^|f@4N3GDp^PYLu@yqSt19N`=Wspq5>-rs(r)4ZX)_#m@X{I+mt% zcFoL9?L51tT}oh6RSeY#IA^OMF?H)SUBlF==6XG1x{M-rC(r-uQ|ibosOxpuCg+ss zb~bF%{_AIIyw#TlOB|Kls`d7ilZj>)pOoqvqrZPI{SjDtW}4BrqdDJa2FVJ{Fle%Q zSu*vhisXZP%M$+78#!Ap-j*_5H9K5S;qMajeUpqc?)35ah|SyRFRh-Qcr9P2QMc>O z?w)5EC(U*9Z^hwDGXrboRwpD){ZUmPu;>Hk^&BNtoal;YKb4A)p+oxh?h<+Fi#OL$q9^=9Q{kC#Y0lz5CzdOs z@X}18=%ZG%!7AaDBO21((`E@cdmK!xU|RC@nL<`WJkR>9jb6FtYHH;nS1N?k!mix2 zbzN)7vp7{NmFu(o3cl=VnJt6bx z3J0sLE)ER7rn2s7W*KKI9v+m>S@iCMY{Vq@ZBlafJ7<-zU+vzuFWpP<>V8rA7Guqo zbNY^~{i2XGd%)wjI+i7Yk0Q0B|1M_>J+kD?a{s_>>kmpF==J+_sMtgx%Jx&+ zn$JclLd*E%pLDYel*Ip;u58qLAoZG^F#DsW>x?r>IB&}fd^TNltWwtCgY>3@=gz3) z?3!frcaP7OmyB6qd2EPhuD1-fx0j?*ID~lN8P(U zni=MG5?iL&Dx3;lxpRx|FYO5XCw<-m@6FWIzPo5lz1*;O|Gyima~CT=GR{xg{*7Vg z<|y+s_n&b*&-XCqpQ7R2GU?akmobesit=VvtX<1xC9cOz`qI|O-I!#jyjiTzbNV&w0>`^P(Ot@}2fW)&ekZ@{D1Y_TZoB*k zuW6g}Z?VJ%hFo8hCMbDe+Rv)&Z;r20P1Z<){C=o)W#Q?(!gwN6cIo`dH|29S+=( zI`x>;=LMU?ncGiWWQO@h%}d<2%)^*lig9Mp6{X3pM^-Oe#BMv``yTyuFN(fu=DVbBY*i7ANHK` z?6+La__Lz%r*!?1@adPj-({R#D;Qz%wd>HN!*U+ULJe0IzMp>o2ZwXGpvw%cD>FCz zT5|PF-HWerUr#>??RHDavRa|p=&*NYqhTJmuay_u{ojWLLnYmk_I8|;2@ah0d7|!I z`BgpV>vNY+e`I&JV)1VouS1`V|KYTq?=AA5tlS?N;O5%#;da-Zt{^2V zpWB~K|8)NP_+I)XU%5Wkorgv9-L1XjRBxYK#QEae1J-pdzbmztDlf6&RofjHE|FxG z*d2OAXiciRF;n{t*>&>|7TVUVdL!5#wBgqI57XD?GtEDHB=4HO$W*C=+ZS8&9$#RQ zcf+2Cb+54Smj@r;ed%Fjy)Jwpc+1P)Z~KonKA6rDCau(@wbz`-#Om-xeft*4dQ-#N zpjXNUgM;Je3HWzKz$Kl|5SFz=QAo4B>b=?dFr|A++% zyXs9&@l1@G;?S4dQl8OVAHpVo=h*5~?+w&AJW^X`ng(_~vr^f&k)bKaSY>Y$e}kHb z@r#F-cpgmEVy^CZuyRG>3-2F4`Sl+q*)(e<2yV)nHtBq)UR=D+yDMuHHt)XhT6ktr zOXa<|_YrN9Q=^`4-67Ox+H}F-TF#?;lRBbTelRu&ubde+zo(pI;k77^DJ%OfziUrk zZQve#aNR}EwKbBfpEiUZm1E+)mU@1Pk>nJ{u(CV9at7$of>mv%zygs)T^Er|A#%F;vMXFe`P+hN_I8>!&#ph zFHV23LYXCOt?=nHTTg0PrcQgKxbRZI8v75m>6TMu+HxYQ*YcD@N7S!KKJ4Yn>{yyjTgg|ckD!~TzqGa8p3R=JWr2^EHM0U-# zm;5ZpZp_}zVcE2oPTCR6_4Bcy8Ex+WielSZZl`n)9fQFlU4~`^}E15%fv_b zle^8+UuB;jF)VayC^YUBSn>8%Ww7qgj^1V2T6w1T)cxO^rE4%v+WS7)!>zF~rjpBj zk7l4qp(#(~DZS-ZdsJ6fE5=(necXPxGWKSf@}|<<<7=m`%*q$peljA!;jGm3eQy++ zju@tD$5_-02yWSMEV80Mf_KXltmr_On6vOrc%7@$rC$-(vR>q+g!~~@wp}j4AEyZQDQ|h9EYo-7+4NPphh_SMMIAd2 zit^sQmCCj;-E>c_nT&o4Y}qygXl6%%N0x8p8++sZOV^aYUv>FE153U9@~;Klwkxl) z_QqN(UElSnK8lHVhqg}dtm6rxoAvZJnuj+pU@2x#Tp4EQvVPC<%XYPGrw<(7BpWc* zbos8YHi=aW69eQPTs+j2nttO|aCsO2IC;wCDEuDW;tM=^iI56-p|0`FF9^v$^gz zuiszTJB_99ljdgQ1`S!pdd<^)r;GN79J`)oxL3pP?q#QqXJl$QQxE@V5c@X2?2*G> z_sIW87!(B=89{d=F~C+vGJyNF0*--+3WbdeH-dY#%#4io0{2eDa4_|pbKegkrTYEV)6yKX4!GLEi1r)9S-2c~fWnQ~vSpN%L;w1APm> zy*Tl5hyBAxrwccHFL8O`a?JCR(DOMubqD9%+LPKkBlUE;i`550a&KNqpW9%eW0^edjFE-z#;t3*>;E5NPyj_9 zB>I@aJE4?Y9Y;$0p{dG2nnfv0uH`l4mEI+^I=j3h4 z?Da=C>Ph^yn{jZyd(P$*f&UDG%QtaNeq_Y$v-i`BHv$FoX6rvaxSDO=9sQ5;)9Pxi zKP){jUVr^x)YI8XKc>C1daSpn@y4ZS`P=?Umvo+p7xT}QEB!l%-HchNu<4ycx}lxw z-d{4;>?iL#RdjsY>YIm`?%!_P_E5fmyPwW!=e3QGGp^qZ|M~SayWX|OCyRLx>i%sz znlSJDt0TvoJnxyOH$}d!q!`t*dp3Z}yxp zyneLySy& zcztfmi8t&Xzn*OSbK_53nQTKd-=_ypTINnyxba=L{B?25!yN^Ca-UdicQbgt>0YgD z&5hJEC)nc-lup|?;bY3R$URf)BjhLQiL(VSPmY`M`pm9b?%y6&s96J)Y4uueHIXYy0A!n%K2xX zeUCi(dvlGkI+wKNkHWjLf>&lLtYKD*ylSyQR&ADBV!G$YP=(g6BA>}lF;+{$G_CZK zRCWm4UbuTstvQ!n=2E7g7w?P&t7R8G`Tgv&6^?xoX%)8Zf04M#W5Jca;IHY;n%7<{ ze2ED!WtaA!y!>ydOB-vnoVUcDLyHC5ceNyUxAvbEntSzNm&!yFPs#aahI>lgYywoK ztuSOh%dzyg`rl0g-5u+vh;n?Mbjt0K%<%~a3{;!h{aP{?Kc1s-=VqVilB0V9m?v#t#tn9J^Gos5e%Fivl3=r}+7#B~uCnalkQhGOL@b4}FVX>YpR`ff(V zT)pQL*&D7uIw<8L^7Y8kMm2j=?^#|mG&eY^lsg__o4)(dh0?kIa+*^#TY0Qk9y-Ud z=v7;zkm@eweed{IsTmjKYI3D~E|IJey;e}zC6aovM0D%r3rpmd<&59^k|mz5>v*wVFD~9>!r?=8As0?~zdIV|8O6+Ib%EuTtM8PGXKR>pWUjXy+_mHA z$pu#$PpKbmJ9D!6*$U^WSD!lFY?izAX<9T>{-Z@HIf0%V0x~YCmkV6z(Q!A`Y&fi$ zZuiE4DT2*1q2a;tRnez1%#{TF80mFNV!?Tfk`{O zGPb?kAz~~TJ>~7HKQrY_9i5!B8}xT^rsSJknx*hPCEV2|xW=MSla=qb=$tJx!%d6M zs;yqZXuiqG#`Nw}=DQtwyi0!;CAQR`j8d9tA?U^}HH**VkA&B8&zdfcUTy}5T2>X! z>D?wzY%X{(E;wMTE#T;(R+t`^^j6?=Q0!?(mgL+yC6=3?p5qtc=yjbLtie3(Jin^g z%Y~^gTt5BQj#P8bzaDZh?biHBoi4KPx_0=z&T~@_I_j{}|7yWXi)Ere0$S55H)z!9 zO*r{%iu~<0es&v=bk>qY+iOJ=Q%}ULST=8!pup|49Wvh|CbKdzN~<|UF4bDja`C8#WDn=j zuA7hYLM|Sio-||LtDnyEMK7k#}w&*kT{C;+- zbLQDDy{R{9cuySrnWN$KV-@EtvzfC+gwA{ZiTLt?<$}jdcaxYs2K&Ol?$TnKZOx!Q zN#dN=UDXVxNrl#G&%2(>FX}NrB3Z}1N6Y36bH~$-%Pj4yrXAQ3{wKh!;)=Q8yNq4kM7-cD)m%L|-Rv5dtlA^)lqN0W4Fm*Rr?{2~_`k6v&5 zwVY^ttA4*TvT=cSUy@tk-;3~n3pM96iu4r2k!zbR|mQZ@M zE#k|?!#vs68$0CEnjLm&E|_um#u;}m`CCWr+(b`18O6$8VxAYl^LT>w=?Cjv=JfU3 z*hqY@jEP8I|9e%*!igO%y{y@jx_6ZPXIR!`-hN>I?)?jNnG~EE<9|iWpSm{0-?C$V z0kP{c7zEla$SJ{hc0!vc~*f#V8x~p(AV2$q+l|-xa3ms}(*h zY~S(j%$y5HHtbPgD`~zvd78WEX2Syyi_VwNYHm1WzG&k@y)yS+jC#LVPCr@R>Jmc>8oTqX(e%HBL57UwA0&fZ|Qw#~>usYtldr=puFLVnk& z@OK9^lAkTKElr)`{%(`ZnSiGUXFS`+^06z>;l;BF3*ut0pEx*qwc*8$a-phIw(Q%r z_k+CV%jf4swYIsepSjt3)sx^$3*Em3>jphJzx>fDM|sx7>$5IP-w6M7c*5Gt>^4De zcUO2EsL=u_-WHSTkXd8zo8Qt6+G57UzJ@yeOgnN z|F!2kG7R2wadLdtt#37JV_?_1a`Rk%Lw%O1Yaqt~j)q6G9k=xt?w@tp?&>0@mQ<^= z#`1ywqTho>rk;J7_TcjKQ!#h6%+0;oy$vLf`p2nGe)gb6EJuXjp|Wdpr!hl7Sy%t| zfBJVN?z&mXU6}ke-au-Sn@qsrPkaBo%J|qgQ)6O88&}>VixyuQmCEiVv$D?13rcdH z`0g-!!G;q(#)bQhUN%c@j{MKS`+K9X_725ylto&*fQmi$p!>d5^s=$R{ zBh8Z!SIh2h`xLY8X3VuE_lm<$EuXn^t;msfmzaere{O1Y%HHHXd3B1*vsIsz&T%!k zI?F@`YYSgYo0eX(yU4x7#Vvix75SBU3iYNZm!6HBm6W-><@#p+hnuc3DHbWnh%Qsh zm7QXdby6e2IBG@qrCHy@B(^2BW=J*8>5!Qg!Xd_QPG@ z>JyH}o5+YMnZ(F0zIwOrq{oz$#1)Omg zpJ6G*X}Dr{}CRjg6cvg=SdoSsY>@wAn3VbNj4fmajfbryR1MaY$m)NujjR zJJBm2EVrI;&@Heif7VMjR+pljx${kziz==8&u~Pf#U=YxC;Ois|FcDcJ*{y9g{iKE zecEiE#jcK)Ya7yk0bqz2o%W1rN2KNy~Un;7PFEnX@!} z&g(4`a8`q#vElag^1MxRSFoS@dBgRPPHBT|(*5ao+vYqy?0CpEm~rc$i?bFS zX>*+SsU~vwGc%8VjbCT&whEgBd}VB2blUMxn6*myE0GD7Jn|;{P+W#5pUSZSSCdogatf@MY?|0^pBVaJ%ZZ1}jLvL6_$$>% zr+d@J{|rGwe{^SRw%lSr`bjf^;rr3`H5KP)@_D9RQQV934XOoZ1%)+h7Ob(_tR{LT{GGsonk_uXX8KPL zb2xwWiJ(fjP+^e$LrwFklPnm&=zn{vD9I;1(b!>XEVq70SjZzG%e231{9iolea>H5 zr2OV*Mzyfa95Z?6R}1DbE4i@DXSk?2Eh{EOs#mR3nni8kp z^mCl&|Eo*X%CyVi_N520UsIA9)@wyCVt+YjU(>m?V}+FpjK}kz_DoTj+~v~xptXT* zk52@LG^agxq<|!6@Qb<()uUfeGi_&jzIwCnLuV1Cr?0l~oxgfbWuYp3#I)YWWmLrk*()jN1+dtZ&fcn2|o$tmycM8-glN)z_8!nzX5PxCmyQ{c}-i z9e=B42hWdn#$RI=rmj1t_-wzkV!OzyXOsl30#>f9;ZoCpw-u|CKD?q@Y=IY51@0GVn?Hs{@fsSh@|B3OoHKvw zlFJ#(YcyLjg!GP2YB6{m=6GS1+T^q5ZWcK~LXS;mI{b)Q(ar5>+<5Gzg%uOuB%NC; z+lv*C-Hn*nv24jKzsZ|cHaQ=db?*7}cbyI=mdelEy>Scgef5WzR`al1@9<1f@!Yv= zisd?o&3Za#9*8%0ofkBcyA$E=_VcW>aZ<&f&%vUHxHmR7x!A^-WhqNsEo1amP1xLX zQRB$=uMYiX2CEA6;&UB8HtoL~qGq|`rcJnG&D@}Z3GXJ)2ri84P@j}}hOczBLa&R% z$CGxm)<)Vr%bn)PFP|;^;ZMNF;t9z~IhHb0OziGTtXnaa_e%Q)cy?ZX>OPJtsGerJ?dMm;)tx&5i| zvz)gROKv7j=Q=9hz@>PU!F26Jt?%I+KHFGRCjOJsI3cOUy7(v@xh#Q}+0?|EE3w8CGrXUN%MT)q;KqJa!q!V9`KDVkXHT<=4NFoN$<`LnS+4RaOUEqG zZ<=Dv4#{PH@t^iAu+T|Md90CJ85ytJV!(UJWJ}uC5v3`N!cED{4;NEMHh=SfYD*ao~LRO-mMfIZ1O?FY3s!W_{Y`+_U(p$P5+s zHztoa?>gM#Y?hSRq3NNLeWGiDqXyr>9Wxd!@YduDOy0bEa%7L(izk^PO}vJy9!SNq z*w1F3liK_+;>U)r^S_2IWZ{rka%XZn!3b~ zH6>0wore=Pv@Ot_al2#Brb)j)^H18ryfgW;m}==n^WZ1nS9Lc`pP_u=&jqyws#od` zH{0A`-7`%;|CQ2g6`yB;4@{(*DmN_@JQ5Pat3I9c!K0Ql&WjXvbmm@5y;>0~_$v6( zD!-gXA_ie0sduNvt%x+7r(gRuoVDMzfXTL7W(t=(pWeKjc#|7P{|V|}xvay$$H>ZT zD1R}#CvT0RqRREDf}PwdaTcC`V^{;;E`BKW(J3sS=U~}0Q3a>S36t#DbTU>hkPT4M znOJtlMql!RqM)hRW2blV%4&w?0_xlLy*z8G!8kQ;(Uj!r$7fbN*n2Fzd$P=%*$n*| z{-H-%3LYfJI|WL4ev8jM<9w~<>CRj`kD?mxw`*rVt6Z`?g1vK-$WcRuOC5%@`kouS zI(11dzp-b}c99RlXVX0lrKUzd^Pe?2LRxs1qJgJXd1a{&$E$C~p{f&KH3;!!%(lMt zaqlzv4SV>KW-E3&InJ+H{%AjwVuUAS>(AwiM{;&tD)dN3p{_K#+v{qTdt54i`{kJ2hW9P~(rE<;c<;4y*tbesOOgOOe`=st8eck*odm4={DbH$U zm>8vzr7FiUY1ITBt657=ZTt|(aA1Sjgyn?+W(HlJ5?Ad%C|r>{#o@51cB;EW&mB1~ zH=zi>>)c$6OT9JHa$G0nxOH*z9A36Yz(DH4A621K*{gQEC=zqJ_)O9@%A&<+|;z`Di_P7lq*Lygyy-d(0AIOtLJb}Ami8C<-Wyw><@lN#`i#;bXPI!1082E9Z-x#a5!J{`SrRFG4y&0i1B_QK- zTLzc(d+VR0EfamKjWIr<8q; zIgv4u;i%X#i;9&&J*6=@g>z#*RLwq>@A`&emQw5F6?JcFugq)7I43=~lRw4q*|(ms UfRt9G120;#n4~!1Xj7khlf{e_9jQ@`?$TKi7vND1J0~9baF|)9;v2$>8asNNU zuvLJ8iIJI^iG>;DY6b?zT1F;j1{Oh9Aw@$+HsQcTcBMiQqsEB~Ih36?9uy6__(8=u zsi=vQOH5osQc6`#T|-mL#MI2(!qUpw#nsK-!_zA`Bs45MA~GsDB{eNQBQvYGq_nKO zqOz*FrM0cSqqA$$M%wS)E2#Ckf{3Xc1#K^?L z!py?X%EH3H$W+e2$iytj!m4P(ZXWYowZ;xuvL#)F*7#z7xMlZq~KiK&=8 zRQ(9@8rWyVd8~;npTRwb@YgK{9%e=cCP8LF2789z)qyFYmdC%Cp1yx#Vb{uo=S3&& zdi!+looU~7Vx1G-tz5TGEacvjiGNnt>fC&{y-5D8N~BM4gYmK2Z2gdp2A&rh>mC1iNJm^u3DJliCWWdXuU9;#my9=8AdwrK{uO|#o?HDj=GWdzZR`aro=7TRTc+N4)XP&TGNf@1^A}N`?kfUD)>n5bg-J@B4NcrK zp?H4P?wHwA)SV(qj)u+>iugCRsK)xiw=XMh6uu2|P7A)B^2sf0&b5|#yjdq!Nlcs^ zaHd@2F~j#o*G11u{saxlZe95@HTqi;C*8WTx&Hp{bo*<&Zm+Ia*{rGPpA$SqWYUhs z+A*Jw|KNYPcJ05(YoC0a|2A{;pV$4nf0RG$j+ebvQD?cwVP1~P)<;>N-u5Leouni4 z=|tcChOg^3JN;eoNAr<=N4T8O=G~ckQ)WEcrDfCkRPwHrS*5aUVe>iLZ|nETz7%?O zI7DE>Rl6J8_vi4`oITvIZ+6+A9mk!6UaWYxU(jv6*>s!x59c5GtL(Jr`XBYR6%Bju z&0X!Eoocq^M`a>QE}QdV1524XU)IMq1=?|~dr&2K;rf(6h8In8(+|X{@4CL`t=^Z{ zy~Tk$r><9K4RzUFp1$H?b!fJ(-t&WX%vb)zKD2cYzVznH@5w?xs>AJSj2^iLtl~c_ znQ)jvrss2b_HGl^MR&fxmG;bTmy8L|PF$|LrBeCBl8ZTI(Z?>iB~MtP)4pm>k=DJ% zWz#Mgp0jumVwyDha`4=bZmYIU*rdt0F=l`Jmg)O;{q|X`Y$>^Ny?NHHPKP?5&#`^o z%Q=!6_?ni-cKK}2&Qtc;E;lumLFLh0O)dtRd50R6I!+w7`*O->y#}xP(#)4(yJuUR zSQYkR&F4RNu17A9TDr%20~>o)*{h%{eUEeZZZ@<0y58cW*b>#5m)BLCE#0Si%_YE2 znt{(SE>~o2b@`q-Jzq?9vtoZqZ+lQXSFrNH+k2~Q8uj@+eumGw+$J#Ly=`I4yv1d{ zM_$U+TvrsS&5Sw|?sNM>?vb~)CuPJA*lJJzxcA1DQg2;VPmMDw)8+5V?w5LfSgm_; z&ctmkKAWW;)adSxxR&?TvXbvckKBQhzgm~BUs$?1y*AV6=<=x{921v`*0L5?oo{*j zC-XsUy60|&qvg^H`R^uwkN#j`x_3p=dG85dbPTkvbu#!0h2^yr23veK>uSzAE34nB z6aIElfJN!w?72H;z70HA7xiXO#I=vDf>!%3PY8Lo!#1d9b>H3Bkr}5Z@>DGeR#e=V z81YZ>QrqgROHMvT8YhmgO?-VUBk}Xv`;1kM_f}@cS(?4H?ffer zQ(b+uwezXR`K|fiJkl@j;yr%L!N8l>hx@0nc;g#vj*nlUipa zeDe1?bcU}pVvTHHg~SR*&CkhfYt~37#4VWB6q>uF@T+NN1RI0wh9fDL7rt*i=vzs=dg#FYC47oaWG_hYeNIR%WH$od2m^b%jd8#1r3_Z{s#( zYh+ZGeC^t?({Kev$GvDQ6yZF9&wrW1ag$E+`JJ-PGoeSw*Jc6xt| zx3jC6UCFbmFT2D4ch;`+LYYC?`{b^aT-lNBzI)pw)*b!tT_U~S$@@Ip`Ci0na!uuj z9Mx@Ws?3(FJ+KV&@tpYcNF-T7wf^50hO5V zXO6ijv*^hm>tjx)@G&eeO%5rJQ@uK?vWrbcxG{7~N6VxI?6PN5Vq+_>tLR&p?p}06 zS^D$jz%N{TThe?XTya6PtaJRlvXfoRliyw4z2eUM#KnFw{G}6NATbIs*4}eCTxg2#;&zk{dJ7mT$z0uAEod7Ia;1rc!O{1 zYY$cW(8~KAXV7u5vy0 zk!6b*&w>Z@*!MbJ6zOVul%2_S#*=+rws_>;-c4V1O21-;i-w4dElxwfNVv95Ui zigix>Vo_Nfx%u)r=~E|^I)C}H|4@2Vb}@yxLiH%J~DF>dKkQgow!y~g_-&KTIbrvHwCwP4~-8up%Cq7^G__Dg_a?`1|Eu%NvUH-tgK-{CH$)M%>+8|7iW) zvtN(##P_8ye9y8eUYA@ki$Bls)xJIDE-33)zSJ^AF>O#fv@d5ZmBXU!&iDzaPH1ZdAI|H%LF z?DNjAwffN)OK+O+7W;W7cD-=g?5<*zT$9(n;#3oVa!lFkhwFQ!SQnpNl=V;M+LLr6 z&wRCcxpQy)XApBvn8zNZ{X%1pouIkc`D+VuP238kHkZG+#r<;C&QD9{ADiE4<@!0Q zV%2%44@+imTeEBJ+)Ic4Jqpp;zqZP6%0AnV$7Y*^)hT?)zIo?ZvUyd;yeG%jaHzVy z5$>D0J}H}HdE8)sk(RUW8eENnd`nyO22M6 z(ffXsdHvxpQiX5xj~x2Aye;v=^~1i*5AStrbDE|#J2jr?Q#_WZYVoBvW`3FIYi$v$ z)H!7z+?1L`1dnxDgfcVKIfPq2^l$y8-jwgQLj8)Vt&_zp2|d&3>$@@p_kB9P``vS9 z^_54Xua$bLswM?*-jkdvT{}WckWB7 zzP4@SD)BU~BaK(CYZl$loUuLe{_zMoYM)#1_W@2g1F9^RZA8swMV@S$|g zK80&?FAo|{{?uf}cjdvlH>YpPwSV}};4BuVZM=f-E^EX`xh0oQKG8h&oPqDz7PT3- zZu~Uh*=NA6D7Kf!mzq9H)|6ZPh`;H=YsFcIkCh!WK6kpqOMaoktWWWhH9}Wv%>FZE znVU^t)nZXyt}fTk!nL^YW14kEk`?tfbCH|~?HpLn9)V+V#e{B6U z{o-jsU3Y%7KlI!A?X76}_A@7pzrK%+dS&PHMDSeYdcMCi_juiU->kTIqSL#nk2SAL zn7+B}5+S^sd86&>2v09_nRO+z$`*?BZoQ$uH0O3!w%{$5pS8V}OD69rD*Vj&^43?0ZI>jSelf2wEd%?v$?rmIW`)e+5 zYYVqE5^kJxZSRi1=ZfcC*I)9=yQra)(Uvtg_SJ_QQEf91@suu^P%QN7S5sz{?bjzW zKD}KqDfvsN?qBU^?{D7zw($Bty&tcCGlyLG^4f!S&UD|>tWSB;We;+b+*RAFzR367 z7tqbM6R9}$h_f!^%j~&J%GAu{uk922J7=GD#(vHk?{(R`mKfbRRkCgSmTlYPXJz_y zls#Y6qp$KsZfn)8(<|p2?OXmbZRgsTbuyRMmFfn4yYIK5A#d8VR}K>wd{_Tjx}NXi ze})`+t{?qJnVmoUXK4TWw(L=)%?{Pg+Z@!sCagKo`dy*$yy$1!rQeV6-)25~$b0w1 z%!QGjlL`) z-Bv#DjMvXh)~#2n@47Gbg}>D-D&(Jvd06HOYnN^oyTab7j*8pn-F#&nb8hBuzdw3c zmH*!Few&c|N4)SP=YNLd(?uWKZ-4rqVa`0RZFfIS+dZZ6_@7VmkHhN({+)_r|G527 zKVP=hJlU^UlPU3${%eYM<;{c`{0+?cobmL$z>*d65m zK)d*n{xNx;Kgt*0mc|SEn}mH2stl-|#WwTqwrLOeZoJDrn(()+Mv`Zx=10D*N=%&H z-?b;ch)(~qF8&`E>k8fNtu@kF@=tz;9+Yp%VE?>i$^4saYp*?bUHk98J^%MjM>E&^ zEZ(uj^nQKItZ*U!XWrjt z3M+V>TFlURK=jhxf2ZSlmltpM+0}egf64JzzTR^tZ|+c;^`k%hqxf6KML&!W1pL_c zC8nG6aqHtxi|(qwcME@de&O?Z+9&ku^W?c+2@7qvns)f+n)A#bOi%y2R^+<$kILne z)y3T@d$(`zG@heUxX*3}m$v@5xWDuMsD6}wcxBeMz01y)mVLi;Xp?)v$AtfF_ea{^ZXa=@gl$iMg&)2z z|Bvzb2mh#xUv>GuJ$O0&%-Nij8vksQ3Kceq^8t>pukE|?gYi-2KZQS97q;%cARREt ztnKN61C`22JvY7|un)>wtZK16+w{WfBI~@eutJ}X$R&Tyrf2<{clLyQX?E6T?g$CC zz%T3c-UTXJN~&$n*>b8pT-SI4zwFCt8*7dfIllEXShezwrD?U#<{Ps&Hu-yiiUY`6W&(_|wGH2kiozN^~1<+aGc zxy_8Xwk2E(il1J<{4@OAKB`<+yh{&D>M+Ri7{>sS3}m>ef-bGz~GwKbW$ zd~cnZ-sZkJ-LFPqKKz&K|?{DihcFcTMRw+C8#-B6CRenF) z8<)ECk>7fk%pa4FY|g%}I@|ET@+aMvfBaXjDJUxTJy&&M+2-B(!Bc)~fAhFKb6Ilpr=^GVmvOTH?E1Bcdxo?8e3o!u z_IGo8my7t8*Inz2baOL2o94aF`pLwnzq$68tkn9my!_#>Fww7@^Q;}&Z-ggZl`vmf zCok;D&f3T7oB!C-`)Pds-l9*(l{S9)zB;O`xq5~06PI(g4vYStygG0G!~YB&leD#T zdgL!$`?md2^<1Z`-abbQFYP&W`QWU*3qRYmE0i0o_R+a4}Lwq zwpR7yzwFLBi62eJIF{_3m*OJm)H(YTp#vo>Tv3>u*=z53j=}AG&pCpI>{mrQu19<2T;=V zN;;P*{m)K``JM3HEBr^Qo1^s37x~)XZa;eL`!PAmE%zs~%~fgPJNi`lKSRp$AHomK zk6Y{WzKrT`-Fot}-_GrQ&$y#=6ZEI`nKLBUXuQ4+N+t&PNcqnf5=!Kr-+iBM`dzy|H2Go9zP3yKle)Ij?3e5(e7)6B z|K{q#-)1%SAJh-)w@#bow$=32tHPM`+K2N~E-^`N+L6^u1XN37G!hIOtV7&hy)U{u zJKRS9k$+$5`CGG(?JAr2uve^f!{$@BqV08dcigu*V;3UBpTHjETRZi`e}+S0_qprj zU)srZaLd3>989JfB-4`!ciUw+@smvY@B);x;1^0)B%$Mx19 z?T>g&ohvf;-WRj`6VA>pj);76tKotCxlNCDsCKG|~iO*hY!*&<7s;cl__1?V3WB(a`2#79a@5_q~FSYu+T!=Vq<75sMHjBf`ffEPFMY>@zR&{J%7<4)-S6ign6=t&B70+UqTxjH< z_IFyT+Lj-aWwh!aKf32~d?mB;&+zO0x#hy*ic_S1_&<2w>E2qWoDtPM$6fZ6_T;~p z3hJHXS8lU1owj$WyO!3|DFP1bx{Nm$T7_+JpPlh0?bJr=_N+@8&V^6^l`dUT;g@Um zxFmamHP@_LM^Evnxd-tF#;#m@W?f1|MWNoi>ra0gUp9SHCwgJcme&r8Oz-v;Y4==C z^iCHtcp1w&Z|k!Y$xh7X%(L3x=lPzUFHkYv_u_+uHw};3nfB-(*{3VDZxM$*}J=<L5VlanPWOVsUN=jF_z2S}N2}kaGNk`w%Bq}pa>4a#xh3<2 zcFr<$@2(PXWG&)&$G^7d><9kCud8LB#K~=Ls#qfAu47o~vxd)+weW29^+WM;73q)O zwSB*(JR?u>z^=>h3e>l&FLN)ool#KRFM8AaVakj*9-HnhDD+%gDsDDQUgpR3Z&ep+ z;#ckcw>Yy<_4TU7-Xd@2@UhEk&-u^LX7=IjNo(~h33iuP{>zLH`Q!7kzi}VGe`59{ z@1M#0uZS+`)6U>Ivx3ijgM5kbfv*a`!kr&-*9sqnd^S^ zo%Xxpv|8FdJd+`RSHY^POFjCF#4SI~oBf}G<<-n@lONmeJMR1PKf^@r{U6ld2xdO4 z7tD;>d%Sz;uHURz!kt7ngnZ^QNZ5I=fKzVj*GVRU3fAs+i$y1|u9$S&uXE;kzbkGw zyY5)-GH(B|@q_W9{|ut*Om5E?x)a}e`RX-|H_rvN^A$GQYuX>O3e~LfUQlEH;p+7E zFTEclZn!8msnxy{SNY^0JgIWM+>i6WQ(nmNel)$_zE0_i>dr2c^y-L2EfP9_xh zl&#B(7c%eAeYoE}t?QBLRP)kXhn0U`Ty!P#?v2+q!5@Wc18)X)X?}mUW9Is&bI*Au zRX^Kv{?YuSFKigAvol3kT*+S=Zx%5twn^om%mjY*dA6)&^Lh6;*yKO7w#~U!+jGZw zWsBX1-4W}G8(&7=JRK61X)TrF^`D_6Y0fN@>x|tV?hB5VOtW&1sbAe~y6Be5v{&8< zg5J|k2fJ$L6a-~XpZ2F`hx}S2>GqQ>g8fmim$xhyIhL*V+KpXjPWhU%kJ4*btav@~ zrSHEB7RM@=`KRh6JV?K^S{olZ(yPuTWkjK0$%yd1G|3?)vteQ|))H zd1T7gOZ#_BcaPj77M?HS_-E^_3WdUnQS(1c@U51d_9%T~yJfOV%9IkmW#?9|+OnxK zqgZ)P`h~D%t;>EcRhD_awrEyJugO~LueQcF!pj$U=}ugFUZFtiaY}gbg54pP75Xlz z%B2S#zP6Y7z&5927mQo^?o4|>>vOG4?WX?>=VnY=eLn8%%Uhv$)U#G{A6V5oZOz3O zYwEnW9#r^qrb?U74knYXcuc$JEZr8|#G!Eg<^4AYKl1!oePEXFxvS}h98cJPzy7rS)A`5o zGqki{-M=yW==ZA9bE}tI%WqgS@l?wk*0K}IA8+wL3>QDGlI{Dh|3YO#-zTNB0_-x4 z5t}jtU9|<4@LgTFBunX$#NE0C_rR!4(UJmoc01NxUCA!FBGP=KqJzIo**b++)5XHA zXXI*CU*G7NvyMYqzO`VU-iHgdmzGpmoD1I@ljC^xhy5duY^BQ{T?$U`B)&2T75?D* zar|NK;(u4|W#8r1xYt@X6{~)|cl@yGCIKOi^B?+WhTll}x;|CNJJ~R1j{KDMNmhdK zIzPCNhVS3Ze|)j+nE z2R@`9(Uuo}S$6%K`NsPZcQh*A>E13f_{z4e#`7by(+9Usv(0N?8AjZ2KR#RY_t~4F z?mEXWq+AM&R`IUAdA3I4@4P*hAByfh{PNiJ+v*}N|CM(WJWkH(xWXq|As>8l-SkK1 z{a^04?Gw7Pb@%a4Z*?B&z5U}?rKP{x@PK^ONjApX!TpVp*Qo7nRwGW%Ecb-$f_Z`LN?P0=}@ z_Y1tT)BEx8(R|^GW!{nbDNSPgb_o{C&fPRet3Q>Uq14{+>-rV{84}}V{;qerW}{eY z_j67B+iP9^5qBDu`oG6#{%2^Mb?@)&H+6Cq@efyAdi%$B(W_s5%jIXTwz{V@JuPTo z*foV#*QC9cs{K_zmmj!w`w#oy1-QnbeZp~m?CSZ4_Vs?a-mhb_zWG*tM|$nY z=$lL246i;5+~8(=n*a2yNB;zCWOw|S{vrHGXSUc|hx?V=*7j|3mzpsDgTk5py_Kx3 z>$b02yT0}9(IZXX2a+!D&5P!|e0}?NyRWmtYSyo;s&T8^z2cRf>i&J#-ZP)LF8uFP zPEFSnA%Us{KK2FcxBX{Gv*)k;&%pjgO^fe8gXMn)X6;9NHh=5<;mLofzjYq_g-2YU z|1QhSd(Ql~YnVKTQ^P)yKF3R-mljK;k%}875yN8B{gU5Q8 z==43c9ZP3idpGrgz6twW_w}Y+=B7b5aq|4$1r-hR{j`tQXU0qaXE?O=!|W)>ZMNw_ zY}y4@$Mzpre_$c^{MNqf*L6$!IbLip|9)O{>)b8AI-5NYxH3%gf1v%z`dibFwdzOj zcUn!`u_|dp@V9D-{tpgI-m2f-nf!Ilvq=}FHH(g(oL0mCC{s^4Q2)$<`6cakY0E>V)(O_h z)7ZAX{qjQbw9dRZ>&e&lWFK}9-Iv;TNz(qFIzye>UVYCC&p4I`UlsLtmhs*3Pkm`g zW|2vZr|<3S|2yMhbv)1O=`ZJ=F6Cff{&`F-^q#Bi5g*pSX`keeKG|T8HvcIbWg2f>UlP6PG{3s(r{{;{ zMdlj)ev@}FX~t7;x!(T_*VixkBE8>Epq%TqkIJs;vrGf0n)-b^nU}0EXTH}hv42UE z|E!wx!6;oJSN64QvRdWxjy(#SHt)%@e_;CPKZD3WzQ`ZHeSfPhm6>p!Q#ZF-sOiJT z*I$E=h5wYxer2iH=itU*I&($FgqYR*`s_ci#x#GPyGQV2{)0WAdYRsb{FdpRZ|^d3 zp85BTp9+87A7!T>vgfPQ{*!(^VRod{{AqmBLD%nIvk2dJHP8Q|`_uT;*L4CJpDyww zU*4K<+xFRXJDWocw!bf5&z`(IB&^F4M-rNM%y$mf-Yc>G%FosRB=|k9ESuyje{}k^Yd=qT z-!`4Ge%n*+`KR_w|JLz$?y?`E%U+s4j)=`W_RW3!#A~-teoA51;5-crIDX z<*;wryPW?FFG6M~hq<@juDe>+@cCh8^y@12eTnFul6_VA303#K3v-QpCRj6^87z_d!0RgSU&hGeMqPxidX1HM$pb_ z-je24#zNBSh3c!CuWvP7d(hgJpYd<2g8Ps1wYT5K99)(6pJCr3|9>ji7rgw>usN@P zc}?+-AFUjni4{?y8*d+q+kWaY-wo}_|2_-H&40L_zK^V9w_Nvt?Qrvv)50CicK2aLyN5i&bUY{7VgFy{p2$ zNXedCtn@HKe{ZRjf9t~yhflUmz7Tl&&X!YB{Ow^k%XcOG)z0(Xu<$q+t9P+Y*$M9r z0oLJ@|AhZoH&61iZ0?5ZL31apUtRT=J*w@BcUR8OPDXBqrKb*>;=>}x0c*q-Mec4tCR`f{=NJ+_4!Vj%N_@o&F#6odFNNN&PQguT<=WW zwLzl5_-I*F=#vAzE4aQXgrA6$|H%7q%I@5COIw&PM)#lEt*zf>r`*Wk>v*;PZt!1C z%TIHsJUvr8al!H0(gvATayO5bdRH!eVOTk9+s5p)#Vh8eO8((o-#Yt|eP_OK&aAV~ z4jFD$(X#gm^;o`RPtv6m@BTB~n=q-~IMBwc|4`ZGWl!pQ_I_~ibF*Fj%>H)XkJAt5 z^Bj|T{Pxnun2!_X<AxGFXO_hCem<~kdVjr6xUJgHH+7LMEAFXr zpZS^Cuin+sm*r;n<>0&-lIvR2Y7fkK@5Rc%V7oGNVPD|uv$cnR8)h8c$KkAIG4I*K z&2J~zFRig_p7mGY?<|wI^Edx%I9PD0@|@$V`l|a6{lvdbG;KeumY6wlhK#{a)!t2p zue4vpx5jDzyRu1e+lAK$<9@E6R~lMv>-EqWB5KOI=gJ=feA@#np$>QPUX;WO?XwuA?|Uf_;uGiw)Gi@ z)k0aSX00uJWNYYn*J|RMj2Dhi#PXstXa@A`5@YG(5f2@_tv`tHd8*&w zxgNQqVz;Zti8P<@=d_>M-+KONZh!ke`8%1jLNE2YM=;QY|engAS zE`F5pZgF$K)J0v<-uV{V8Z7S)vJ@2GU%39<(U0F-|4F_2$NZ7KsZ@OD)h!)Itd=-& zDv2jN*{RyVZavp-wypM^`pom)R^>f)@)c(9ZuxJ$xo@@1gNYL^Zpocc&&RQBQczfw z;f-JCBd`Ave^}4t`Pkm(enq8{=h16(=9PU({bkL3u|Me_@|$)37#G{_ zTV5&O@lZJPu;1p-^8%bEEXiXrytk(~UbDviqxT=K<|F?kHw(S8=d+2HU;`V`5yZ-ArR92Fq{jHGh2nnE&wf zAHC~qxBiXy+1vE);?ccbev{687PX6XWmjlBc=wn=R{qV&$1a;6^ygXMp+EKgG_i;? z>u2&Xo@g(tJa?u}VsXXis6*}x?dBRyo|1oRLBGqahdX;dWhYeZ_p$iT;NAUM zc}A><N3Rg@>V@7|tE zySJW>lJ(pDsQgpPG`Cv~>67ElDt1TCK5D0!-EP0T|9hdv8{2xRFZ(;|l*FdhC-y5< z*2OP)A6K~O<^9Yn0sOo6%zAz=+_dlb$@yDj_fLNuYx}YHTXns9{>Sw~w%RY|-xl^W z`}MwEui7`?K)$8nKf{}=pAX1pPqT{UTlU!S=Uw+u5NJV-<8d|cv1IeD`^=f>uYQD`=gF*Nqf+Bwc%Zy_PLcM)g`G*&+pL7 z`!Iuj{?mB7x~p&UY;+gzGv|2Uq5E>i?fy%9>)r^ZSnvyp8XYds!{dWNR}V*Z z+_Goc=Pv(fu84PKk^4Q1tS1W2EX&Lv^l_NJuG6@DM5@PK#zLchZsq&VEsL`a=NS~< z+r9loO4a;>{~2UVckx<1Y}Eel`A+)`jG7G8P7YrHpyN65`VZ~ z=6F3%;R(LmOA z%J6Z@7Dlb@nPuZ0KJTGYsi%ZYnd$TEyXw~OZ2Gy0sp8q*^T9tdi!N>KN?@z*xwftI zp|-D@!FBnULVZ;w%eENXWz0VG>vPYHlz`gKb=hG*B5uuFnZF|b=Iq(06FcG}PFE&d zK9IAzzPqOG$J^`=>Ayw!x@N7J)iI+byI;jkIDOH!;@!8GR{Z{V$HVi}AO3{;`}>m5 z|NZD6*K~X3MD6BRO|jN7>N!f=ZEXcO#`Tw{G$Ac>5anAO9IX96vaH(v6p5 zMiqfj{#~s;qwL9#ty!*Ab;c3ZR<><-pC4+6dOV*uty{xa`be+$Dy>9Ev&o6)ZuCU(| zPp1*jd*e?})tHS3pY6`xvG$MkQeTtSB#tFdKE~=)mF}C$ znB<_e$NJIrp4-7Yq(axU9OHlQVY^o=<(IO@>N#esS9-2{^z*`nYg9MiOD9<;8ZQg%1PgSSJt>%$4ReT`_|6P;&I>9^S36PVSM>I z_I$fybce(Fb9)l5&kVa5HSeBmv9;X6>h*sg?nst+aGm+1(%-G}Tv>tX3deWLJMn*6 zviquek;K;*+T4*>>MkZ0i&d7SO3E#`HnA&e@uyuOJ5%N-A1|JB>Fu(|EMFhZS>71e zWOtT}jZM>Cq~W>A2hTqmk~$Xmw7*t=%WN<|FrS^t{+X=gpOg02_G{`UW~REh>b!9X zmGwMZx2J~hg8GN^hxz$me2=+vGvX-EVp-`tkH3ZCylOM|*l|kQSzYdOQsti1$1gYa zAtL3zt^`rQslXS=9?R%!0 z_lfpbeLbxkq`hKOk3sgszxsjG*zPP@&GmIXb4^^e(6sOG%KBL5-TSibO7wi!-R$Sw zTobdlZCatqeyV=A_mUq?_k*{2E|}ucW1+Og_s|)R6B4h(=AOA_$M$!>&5>(1QoCo{ zxn7t5pz->ieR{0i*?-(WZ0DU5OS?Q_TE>)VmNt{SKAyjQ|IPP(<{yquC=K6!`N`DH zuRcw%{}NlP_#xTTSN@@T^8y{tSgkvgtcn`Bc~=f~;aRzK<=nYZWNI`6mr za*el4>RbJ_iv%TQN<5i*jJEsCa}Rv~dHS33k9z+Z4$T+pZ(WyGxI9~RQFfV)Ye~bD z{G_rec01*!ewSOk>edaX&*v>X7P8)*@jz>xr}_=X_}TXCrfeVi--<4Zy{UKO*)_Qj ztp6kq+qC~@kX>l~LObG@`L|0yZdX3MYj*lzRmxeGTT|4Y{mVEfed1(jw{5e4=T|Ov zr|emO=hUaoH9t}%yS!^pGV|SoWj3dEH4I%0pLeP%Pu{($r|{&t!nokysdr=Kd&~6= z)zmJ(?Q#D*cMYHYgXq<7|1&Vh-S&NOmn&OLH|3kzGokk0c~Or#UThIKy}xNb?>e2{ zE&sSKh1n#R1XSkjcaJfv{Twg$@A7?)$q&A*%v}AW?A0S*u6MB;f3E%g+OjP|?_*CB zTj;J2jxQ&F*Zuo2)ZXyV>!<%2Zkq?S+R5CGI`ebK?)^PS=6>ip+W&BZy_8>&t4isA z2G)JXS@weaq$1*82}^FZkX);`#QxL8%M!)!PKC^`x$e)}a%;^`>lu?TT%Yyt_&$Nc z4{P&O-IzZ8$_%!>kn8g=>*`nkmOVT@2Yc!@o?EQW4v3bF6Z}wiG$>i+gXhsI*S%B9 z#15Bafq`F#EnsqVYyo_5Pmx0||iS?1Z|LK&~+E7nZfx$e|)&j+>) zcVgD`>`7PK)$6(M=40#XRO!WA+6#|=pZ%X>T%9Q;CAxk7 zs_5p+7539jUre9>(WpUXhw%cgP39ltyJGJaSJxZQ z^>U+5o@8uV`QmQKuW;XotM?loWS_M2Y}n3L{os`OIt_^_Z@<{xKegH9r|PBmr#zOL z&G&k^nqB3*TWeg{x)O7#nJOQOUq^sGuizT1DOy{n4P*U;TmCyTq3Y2H!g8~aoF+m}C5 zKX#w*#-6y+xm)(?Tb@lE<6F-h~?p2?4ZI8`e z=b!VRpr zF5YmJP}f?{y7OqJcr8b{o9nz~^Oz?@n7;CBe-jD0IRMUB?YCktDqXk=%D+6?F<-1k>%yMo)t)O~-KvhccS1BO z-tx+X#mozjd#zqpcV3R&h_QHC49~o#cMkloeUJV1ydJIG_2cI6=&bpICcoDTJ2mC% zd+(Hs`Q-87yw&q}g@;zYWAl6&yZ_DX5361uI&a=xdPDBkuSfc7r)~wd_nH0vWbh_} zf#DTf@wL6uhw54Xh(GMS^w|Bvg07bZVU@)u4Zn>i_aEBJci-*Sf=x2Nq;%G3+b!>_ zyZWDjJMH84_O~^`OPsD-wM~{*JtNq&XWNr^ry8#;Jb8Svyw~UchWlG5{-|Ai{Na9q z!=}%AIqg$+HS_9jP*>$pf9CU~@3HN1|24PFwsrhD7=C%@dWDb2-<*ALU+&)NnhoBI z?w>06w439!rsaFa3GMlXT~Sd7rf${E+Q0RC!`d}ZX5D-DK47|JbVcq=$^M1<4cgCb z{r)o?jo)g1-qi=ycRb)bw0HI4`VGq(buZ06TOG2y+vd1T z;nDd&#CU7tKP-N*tntHV&uCj|pLV}(Dv}a4mwfKuE4SJB>z~A`uCDT`uD34}?{n{y zj`|UQ_*UBFv>BT>HY)ThPht`|Zh%l>if z_uM{Fpsgl>=j)0oldjHkGt;T~{)nY2cFC$qthVf?pW}9~^M91<`*dPwdpWah_Pmef z$71<|uB&d@eO>3_)aXQKeTHA@3;w*$cisNGz^$EUZPS|N3jG1u9J@DXM@HPZ_-2z` z=sBzK#TEL8m$`pl5@&og{YU!YrQewL%t_lD?RDwWvAcl|=d@FkOw;UpOXc7AbH(I5 zyj!MzpnEZFH{x+i%^(z1S3+lsBu$xF7$s`OlsVa9`o=et zpC*1+VV?TUZqafLhJRDe?r;CPCVaMLY);X1sWo06_jh05Iy*gJQku1R#@Cg)Ul!dF z?q9IBo1b4&f|W%+Of*Y-<5?~(b#s*>{+XN4uQk9dqa7b#TN}O1-}y&q$-(dI_CG9dRr@%N);HEaWc@p@Mtk|L%5rnFL+=vrY*}7;{trvzkFcv3H6L?-mH0VTqAud!)EC>X z1-|?yw(X)uL}Cvs^QOkexb#PyEphsh?9vzXpZJyr_v`4CUa9+f_R#X?Sx@RzG7rVt zpAEc_-WWT_y~eRU>fA@ug}W}P#_%*woOMg*`6L^@!?Cs60n#Tw&F*@gSCcC?by3_a z*6&N#?wLI2^LLSL_Fm7w?qxX3(zkjNpAl=ptfzb5%+g!Btg=<;-97Kxd$Xsk|GKJh z_s+>xa^}C4D)w2=za{T=Woza4n6uw@_r5ENOY1!U%+vJy)9Xjp@*j$mzEZk3?Md{@ zZ&@j2ug;$5TD)7xf%(eyr)86u+{@U-^rPRqtUKbvzopyW%Le+{Z2!d`9{lF&otlat zx7Ck$>+`*g@_BbHNoCKD?Q#1vb5`{Hn(~DI`)bv4-H2_Sui_N`uD8iwUoq>`XP;TO z&ij-f-8bvo$1@ujJV@DaGWp7!uQuVgZs|Abe%0s8ob)Qd!PHVWxq7{x?doS!SwC3w zOpc!+$A0nMmiV41{no9y%L-TAI%px&xN~C1AI76~nZt;Tnn7gxgNQPzRK8#fAJs+xGU@4Q{XV3MG|5ow0 zuJ~3fiA!5Nh4MvS35vBgvP2#!vbiRI&*rV%k#El*+6z^%ADtE7Qu1CVYuehsQ`f|L zikJ)Ay;Q9c(>uIQ`q@F-viZkjqjo8slwTMn-k7qrQ8q9|)se@NrOb4xdRyLyvLg&H z8?@%XS^beszhgdIiFfB5yT!}bf2+M=TYiio%sxb`-skps_l$R*{?Bk)qk-ku<4M)wg&+PieAE1}tT$_Woaz4Q zM{XVd^Xb^6EoP^VuV6@=|M!`)-PD?G;gh#N^WOSqs};xotozOTo&Mh$c4Duka<*-|#ZzT_ zFi3gt<+;;5Zf`uO8vVkxa{rbjj>$!8uFary5O`>eOY55M>8?9p75Y4&S!Z1l0GPo`eH=3W-CSl*#t>cfAAzWZWZ zwL2evxHnN}^6T&iI!!{}$JSm9|GVt#8_V|*yKFZ9ar^Q7(SCuL)&61~eHRxOg(!Zy z@{cj*udnZKor;tn{~3z?p^|4!ZKweftoo-3ALKxf1BU5UA==4v7_ zZTSgjFO(cOU-ey9{_wV{8t*AFT`zLxcsx{Q(D%CbGGn65+Red-ei<#hbXekT*R3mh zi#KhbSFRY!8gco7*fxd7)7Hs9%3J>=vYTV}_eq~_d|C27mp5r$(LSk2)BQqs@1Nkj z^F!|N)+*aCO{YC)e(q6RSA5Va{Kl6jDv5WVZT%CeK6$ZC{ZTdX`NctBVpi;U^)6X= z!sNqY58ECew3OTWGx+26gU=7fi`Y0W*Am(O+uNUAMx-br`Q;>guD|)-^*<)F$W9S_ zAJ6^ozCG{Rt@oxs+FfcLQ9J7~^Dv;kbM1PznjBBbXIr*J%+^<6X*{sz zlJ?RIx;|xzCy#xebx?WA%aWto8OvqnZB15JzqB^`h{|RMi)p5k^7rJnvVWBK@^_9n z?mc0Hx^iE2NZr1DQ!O3{R0T`c`eo(jwMOjBU*w+SmOEuat+t=E0K*wAJ}aK~WW^`rQ1pF;4S_Tw#3p z;T+|O9_@uP55Aaw4)57=c*&2&!CP;Ms2rWbH#_>7{6djg-=p0`mD7tAS!+H2Gq8OB z5M43(@!4eyF23J>@$Hh`Y03(g(!w843;zf|{h?>h#LJiVhwe}K&(PfdaQR_xU9W%l zPhT@V^6%o@h{Q=shFtPL_|N`lU|P{rc~(QediKUIZ-0JzobQt>`c%%c=kSV~cX%53 zIR9i{`D*s}*7~yNq6ceSFQ2?5RCv(s^~>0cZ?aW$1Czbx#6++2mw2W1WJB$uOCBHR zX>%Xi-dUWp^Y@LHu`1r1ZN4tjX{!`l&ammq(%$xWcOFjpwyrw*!=p2;j{{CshcEo` z*o!ULKxW?7(`)o5iaAB_E!y&<)ZOyq@xyyX^XklM951AFpWV1aTvLao;DDFKe}?1z z&m-k|_i@HW{3)F5y`%Hl=8OLsE>8=KQ*o*-x3S{a{qzsB94JF|TI_xz`~B9C15x_M00QT^lk_J1b# zyc2(Wo?T&)u(CQ`Bku;cy#J}a?B7;@{QUBs^tKOo_bfkm<^H+(?DJLm8U8cux$&Q2 zpRc<3nWOTp^HP`oIJ?idCd2zbgK+%u^vsoUmD_{P8}ueOR=KV5V?V{j_1pMy+TMrf z1+gX+;1gS+l66_qXl{H3h*Qa@flT>s``^+)DIf!V#Q|1+d+J<_At z`nIR>u}r>t!7;zW<5{u->e!C@=cldmvH<{ zJHroZ`#v*n;d-7d%>|GzH?22`{5PQ4% z&_09Lzn)YJddAN>v*V9C?{DFXy=_{b&wEvKcUjaeoe-_F1P=`r)OURx$reRbM=?e6)?Jy#Q&ZoZ4l zSiITgv(&>o99IkH9^I8J`}&L>-@nVV@_X&{?o>qC+IepIy^^OY_K%il(JF6~t}5k( z3DrC);b*1z-+cerd}O6u^WW(!of9PZ^?rRmz16Sk!A6!^#`PQR)9Y_`9e;4fzr46; z*7mM-k3YTB59H{&rF^naq4K%R#wU*@m{+$*<(|(!v##XoqOG>KUcIx>jZ1RqGCcTl z*2mkODIeAL#XMPAXWLq8FKBu|u;g@_HT(VBp*F&cJFeV0!N1ihyxeT-)V}4u+XU7W zHXiCa@^R_$xs|C|R+5a$*F~?rywE?-`9k?Rwr{V0dmmlWyfg9f*M&E?^eSxGc`NeJ zvOD^L+a=a3{asMQwa@%V{ff*>8g@Cp3@WwP{)zpOX4~8D@1{KQn6`S{<<=+V+jjN~4jy_R}Bc6s|>zIrb4y(P(^>aslbH;o@0nJ-nR|I*6m z;^vHR?Qd^QW;{NjrDo^Xv_t>Y_Q#ogaoMp$aD~T}rMt9x7#43>Kga&T{CLjbhtCh5 zwDGOvpT7D>&)+4}<&C2CT)f}Ti~cD3C9n6>%a4BkMqVlQovW8it7S_RxgGm?+4t_5 zNft*kk0$*%@8^A<`|evwEl$x3udcIMJ8t>=-fOLn((A+G8I>pOPi^b#DeCL_aGxWK z?be&abNH^l_$YfY+VrupuiEsL?<9Z5vwvP6{Nde-?blY7GZi|=tT~(2EvImq_3!GG zn!^j%TTHs=Ej>Mm@7|2{G4b<^_PXyau6o{nWbIye^}Qjn*OvL2UHQCvx8jant#tnzQIm-Mg^h7nUL~Q)fEfR6MyVC_C6( zbmPqI2fFGWmmhB3C~z~%p~+$ab4IVb1N+kC`!Bzpc&0Kjru@XC?b5zG_FGhPKi?p$ z9q<05POUV}Ew8?7*6uqx(~eL4vG~Eh?vF}Gbxf<%=CMrhmY?!eQhMTq!xIl4T>GD) zZT@Dli64IIMZ5RCvv}tb#5>xpw5>Ni zBP(L>eC92Gux;w2xni~Yk(U=<(MglHFVVpRI%ekE<+xhpjxA2d%*uq}+)w)UfuwZ+W>mABt3SALrn`Ks;ZogeGH z{ycK!Y z8ZMhOLYZ5ar+wE{+2XtSal%h{-~8`)uj*Ym|)FFZ*q+ji?gjWFY$-Lqb{ zd2cHY-=~|c`+K*>zVs5)SD)1}R*OmFs!q*eS z)8v9$kB07uxx4B=L#g}cxER#|C;~|d~3)6 diff --git a/libs/ultrahdr/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y deleted file mode 100644 index f9d8371c18..0000000000 --- a/libs/ultrahdr/tests/data/minnie-320x240.y +++ /dev/null @@ -1,1930 +0,0 @@ -ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}vrswqnpsqqklspmjmnlknw{’™›œ›š›››™›ššœœšœžŸ¡¢¤§«¯³µ¸ºº¼¼»º¹µ²­¥ž—”’”¦®´µ·¸¹º»»¼»¼¼½¾¾¾½¼º¹¸¹º»¼»½ÀÂÃÆÊËÊÊÍÍÌÌÉžºµ±­©©­´¶¶¶³²²³°¬©­²³¯©¢¡¨±´·µµµ³­›…„•¬¸º¹··³¨ ¦¯±£W9ÙÙ×ÕÒÑÏÎÉÇÇÈÅ¿¿ÁÄÂÀÀļ·´±­¬«®±²°­°§Ž›­®¯¯°²²²µ¹»½¼¼¼º¸¸¹»½¾¾ÁÅÇÆÉÍÏÐÏÎÎÌËÌÌËËËËÌÊÇÇÉÊËÌÌÌÊÉÈÈÆÇÇÇÈÇÇÆÆÆÆÆÆÄÃÃÃÂÂÁ¿½¼¼½½¾»¹¹¹¹¹·¶¶µ´µ´²±²±¯¯­­®­¨¤¢¥¤f96999:7464336753/01,..2344N“žœœššœœœœžž¡£¦«®°³µ¸»¼½½¼¼¹´®¨¡›•”–𤫳¶¶·¸º¼½½¾¼½½¾¾¾¿¿½»¹¹ººº»¼¾ÀÂÅÈËÌËÎÏÐÎÊÆÄ½¸µ±¬¨§¯·¶µ´²±²²°­©®²³®§¥¥«°³¶¶µ´¯¨“‚ˆ¡³¶¸···³¨¡§°´²w:ÚÚØÖÓÒÐÐÍÊÇÇŽ¾½¿ÁÁÁÃÄÁ»µ²¯®ª«°°¬¥£¬“¨­®¯°±°±µº½¼¼¼¼»¹¸¹º¼½¿ÁÅÈÉÌÎÐÐÎÍÍÊÊÌÍÍÌÌÌËÉÈÈÈÊËÌËËËÊÊÉÈÈÈÇÆÅÆÆÆÆÆÇÆÆÄÄÃÂÂÁ¿½¼½½½¾¼¹ºº¹¸·¶µµ´´µ´²±±¯¯­­­¬¬§¤ªr'Rouwwvzy{zxyzyxtsvurkgce_F U¢žœ›››œœ››Ÿ ¡£¨«°±³µ¸»½¿¾¾½º·°«£›–••˜ ¨®´¶¶¹»¼½½¾¾¾¾¾¾¾¾¿¿¼»º¹¹º»¼½¿ÁÄÇÊÍÍÍÑÒÏËÉÄÁ»·´±­§¨²·¶´³³³²²¯«©®²³­¨¤¦°³··¶µ³®£‰–®µµ¶¸¸¸²¤ §±¶·OÙ×ÙØÔÑÐÍÎËÈÅÄÃÀ¿¼º¼½ÀÁÂĺµ²¯®­­®©™”ž£–£ª­¯°°±°²¹½½¼»¼º¹¸¹¹»¼¿ÁÄÈÊÌÎÑÑÏÍÌÊÊËÌÌÌËÌÊÉÉÈÈÊËËÊÊËÊÊÈÉÉÈÇÆÆÆÅÆÅÄÆÃÄÄÁÁÂÂÁ¿½»¼½¾½»¼»º¹·¶¶··´´µµ´²±°°®­¯®«ª¥¤@: ¬ª¬©ª¨§§©ª§©¨§©¦£¦¦£•¥£¤•;:—Ÿžœ™š››œ›œœŸ£¥§«®±´·»½¾¿À¿½»¸³¬§Ÿ™•”–ž¦­°³¶¹»½¾½¾½¾¾¾¿¿¿¿¿½¼»º»º»¼¼½ÁÂÇÊÊÌÏÑÑÐÏÌȾ¹´¯¯¬§«´¶¶´µ¶³²±¯ª¨­³²«¨¢©¶¹ºº·¶²¬™~‹¢°±²µ·¹¶®£ ¦±··¯oÛ×Ö×ÕÒÐÌÍÊÇÇÅÁ¿¾º¸·¹¼¿Á¿¹µ²¯®®°«—‰‘œš¡©ª­®²²°³º¼¼¼»»º¹¹¸¸¹¼¿ÀÄÇÈÌÎÐÑÏÎÍËÉÊËËÍÍËËÊÉÈÈËËÊÊËËÊÉÊËËÉÇÆÈÇÇÆÄÄÅÄÄÄÃÃÂÂÁ¿½½½¾¾½»º»»¹¹¸¸¹º·µ¸·¶¶³²³°°°°®­ª 5M¦¡¢¡¢¤¤¦¥£££¤¤¢  ¡ £}>Ž¡ž¥I1‘Ÿœš™™›ššš›œ›œ £¦«¯²´·º»¾¿¿¿¾½»µ®©£›˜–•›£©¯³¶·¹½¾¿¿¿¿½½¾¾¿¿À¿¾½»º¼»¼¼½¿ÁÃÇÊÌÎÏÑÑÐÎÊÆÁ¼¶±«ª¨¨­³µµ´´´´³²¯©§­±®«¦£­´¸º¹¶´°¤‰€’§«­°³¶·²­£ §±¸¹²•ÛÙ×ÓÑÓÏËÊÈÄÄÃÀÁ¿¼º¶´µ¸½ÀÃÿ¹´²°­¬«£ˆˆ”›Ÿ§ª«¬°±¯°º½½½½»»º¹·¸º¼ÀÀÃÆÈËÎÐÐÐÏÍÌÊËÌÌÍÌËËÊÉÇÈÊËÊÊÉÊÊËËÉÈÈÈÉÈÇÆÇÇÆÄÃÄÃÅÄÃÂÂÀ¾¾ÀÀÀ¿½»»¼¼»ºº¹¹¹¸¸¸¸¸µµµ³³³²°¯¬5T§¡£¢¤¥¦¦¦¦¥¥¥¦¤¢¢¡ ¢T1s¥Ÿ¢F)sš›—–————˜™››ž £¦ª¯³¶¸º½¿¿¿¾¾½»¹±­¦Ÿ™•–›£¨®³µ·¹»½¾À¿¾¾¾½½¾¿ÀÀ¾¾¼ºº»»½¾¿ÀÂÄÈÊÍÏÏÐÑÏÌÈÄ¿º´­©§¦§°µ´´³´³³³²¯¨©±±­¨¢¥°¶¸ºº·²¬ƒ‰˜¤¨¬¯²µ¶²­¤¤§²»º²¬ÚØØÕÐÏÎÌËÉÃÁÀ¿¿À¾º·²°²¶»¿Â¼¸´²®¨©ª„Œ”ž©©««®°±°¸»¼½½»»»¹¹¸»½¿ÂÄÆÆÊÏÑÑÑÐÍËÍÎÎÎÌËÊÈÊÊÇÆÉÊÌÌËËÊÊÉÈÇÈÈÇÇÇÇÈÉÈÅÄÄÄÆÄÃÄÂÁÀ¿¿¿ÀÀ¾¾½½½¼»¼»¹¸¹º¹··¶µ´´µ´²²°­™2Z©¤¦¥¦¥¦¥¦¥¥¤¥¤¤¤£¢£‡HeP¡ ¡@&-Hh‰—–’”—ššœ ¤¦©­°µº»½¿¿ÀÀ¿¿¾»¶®©¡œ˜•™¢ª®²¶·ºº¼¾¾¾½½½½»½¿¿À¿¿¾»¼»º¼½¿¿ÂÄÆÊËÎÏÐÒÒÎËÇ»·°«¨¦¥¦­²³´´´´µ´²­¦©¯¯­¥ ¥·¹»»º·°§Š™¥©¬°²µµ´«¡¤©´½»³¯ØÕÖÕÑÌÊÊÉÇÄÁ½»¾¿¿»¸³¯­¯´¹½Á¿»¶²­©§­£…ˆŽ—¥§©«¬®­®·¼¾»¹»¼»ººº»½¿ÂÄÆÇÍÑÑÓÒÏÍÌÌËÌÍÌËËËËÊÈÈÉÉËÌÍÌÊÊÉÈÈÇÆÄÅÅÆÉÉÇÅÄÄÆÅÂÂÃÃÁÀ¿¾¾ÀÀ¿¾½¾¾½»»º¹¸ºº¹¸¸·¶¶¶µµ´³±°–0b­¥¦¦§¥¥¦¦¥¥¤¥¤¤£££¨_gA‘¡š8$A,&6W†—›š›ž ¤§ª®²µ¹»½¿¿ÀÀÀ¾½¼·±©£ž™—™ž¦«¯²¶¸¼½¿¿½»»¼½»»½¿¿¿¿½½»»ººº»½ÀÃÆÉÌÍÐÑÑÒÒÏËÆÀ¹³­©¨¥¤¨¬°²³³´··¶±«¤¨­­©¢ž«¸½»¹·³® „ƒŸ¨«®¯°µ·²§Ÿ£«´»½µ®ÐÌÎÐÒÎÊÇÆÆÅÁ¾¼¼¾¾½¹´°«¬®²·½¿½¶³®ª§¬°‰Œ” ¤¦¨ª©©ª¶¼¼º¹¹¹º»º»½¾ÁÂÅÈÊÍÐÐÒÑÎÌËËÌÌÍÍÌËËÊÈÇÆÆÈËËÊËÊÊÊÉÉÈÆÄÄÅÅÅÆÆÅÆÄÅÅÄÄÄÿ½½½¾¿ÀÁÀ¿½½½»»º¹·¹¹¹»º¹¹¸¶µ´³´³³•/l²§¦¦§§¨¨¨¨§¦¦¥¥¥¤¤—?S_8z¤”06‘}V5$.YšŸž¡¦ª¬®²µ¸¼¼¿ÁÁÂÁÀ¿½·³­§¢™™Ÿ¥¬°±´¸»½ÀÁÀ¾½¾¿¿¼½¿À¿¾¾¿½º¹¹¹¹»¾ÂÄÈÌÍÑÓÓÓÓÐÍÇž·±¬¨¦¤¥ª°²²²³µ··´¯¨£§««¤Ÿ¡±¹½»·µ±ª“‰•¢ª¬¬­²¶¶¯§ ¦­´»¼·­ÂÀÃÈÇËÊÇÅÄÁÁ¿½»»¼¼¹¶²­¬«¬±´º½º´°ª¦§±¨‡‹’¡£§©¨©«´»¼º»¼ºº¹º¼½¾ÁÂÆÆÊÎÑÑÑÐÎÊÊÍÏÍÌÎÍËÊÉÉÈÄÆÉËÌËËËÊÊÊÊÉÉÇÅÅÅÄÄÄÄÅÅÄÄÅÅÅÃÀ¾¼¼¼¾¿¿¾¿¿½½»¹¹¸·¸¹¹»ºº»¸·µ´³´³´”0xµª§¥¥§¨¨¨¦§§¥¤¦¦¤¥vXzvT^ ,C¢¤¡ˆ_;.2Mu•¥¥ª®²µ·»¼½ÁÂÂÃÂÀ¿»¶¯©£¡œš›£¨®²´¸»½¾ÀÀÀ¾½¾¾½¾¿ÁÂÁ¿À¿¾»º¹º»¾ÀÃÈÊÍÐÓÕÕÔÒÎÊÅÀ¹³®¨¦¥£§¬¯±±²²µ¶¶³®¨£§«¦Ÿ›¦µ»¼º·²­¢†ƒœ§­­­¯´µ´¯¤¡§­µ¼½¸°¹µ³¼ÁÄÆÈÆÃÂÀ¾¾»ºººº¶³±¬©¨©®´¹¼¶±¬¨¥§±—‰™ ¢¦©¨©«³º¼½¼»»»ºº½½¾ÀÀÄÇÉÎÑÑÐÑÎÌËÌËÌÍÍËÊÉÉÈÉÆÆÈËÌÍÍËÊËÌËËÊÉÈÉÆÅÅÃÄÅÆÅÃÄÄÄÃÁ¿¾½¿¿À¿¿¿À½½¼ºº¸¸¹ºººº»»¸¶¶´´µµº“2‚¸®«¦¤¥¦¦¦¤¤£¡£§¥¤¤qЧ¦ŒW™ƒ'LŸ›¡¡ŽjI93Cjž­°µ¸»¼¾ÀÃÃÂÁÀ¿½¸³­¦¡ž››ž¦«°´¶º½¾¿À¿À¿½½¾½¾ÀÂÂÁ¿¾½¼º¹»¼¿ÂÄÇÌÎÐÓÕÔÔÓÏËǼµ°ª¥¢£¤©®²³´³µ¸¸¶³®¦¢¨ª¢™š«¸¼»¸´°ª˜€…“¢ª®®¯³²µµ¬¢¡¥¬¶¼¿º²¶´²±·¼¿ÅÆÄÂÁ¾»½¼º¹¸µ²²¯«§¦¨¬¯·¹´®¬§£«®Š˜ ¢¤¨ªªª³º¼¿¾¼»»»»¼½À¿ÀÁÅÈÍÏÐÐÐÍÌËÌÌËËËËÉÊÊÉÈÇÇÇÉËÍÍËÊÌËÊÌËÉÉÉÉÅÅÅÅÆÆÅÄÄÂÃÁÀÀ¿¿ÀÀÀÀ¿¼¾¿¿¿½¼ºº»ºººººº¸¶¶µµ´´¼ˆ3ˆº°­ª¦¦¦¥¦¤¢¡Ÿž£¤¤£ £Ÿ››ƒ$W šš› ¢žŠmR.T­¶¶»½¾ÁÃÄÃÂÁÀÀ»¶±«£Ÿœ›ž¤«¯³¶¸»½¿ÁÀÁÀÀ¾½¾¿ÀÂÂÃÂÀ¾¼¼¼»¾¿ÁÅÇÊÍÐÒÓÓÔÔÐÍÈÿ¹±«§¤¢¢¤¨¬°³¶¶¹¹·¶³¬¤£ª§¡™¡²·¹¹¸´°¦†ƒ‡˜¤«®¯³´µ¸´«¢£§­¸¾¿½²º²°¯±µº½ÂÃÃÁ¿»»»ºº¹¸³±¯¬§¥¤¥§¯³¶³¯«¦¨´ª‡”¢¥¦©«©ª±·»¾¾¾¼ºº¼»¾¿ÁÃÃÆÈÍÐÏÐÎÌËËÌÌËËÊÊÉÊÉÉÈÇÆÇÊÊÌÌÊÈÊÉÊËÌÊËÉÈÇÇÇÅÄÅÅÄÅÄÄÂÀÀ¿ÀÀÀÀÀ¿½½¿¿¾¿½º»»º¹¹¹¸¸¸··¸¶´²¼5Œ¹°®­«ª©§¥£¦x𣢤£¤£¡žœœž€ ^£œœ››i‡­¬¥X/˜º¹¼¿ÁÁÂÄÃÂÁÁ¾¹³®¨¢œ›¤ª¯²µ·º¼¾¿ÁÀÁÀÀ¼»¼¾¾ÀÁÀ¿¿½½½¼¼¾ÁÄÆÊËÎÐÒÔÔÔÑÎÊÆÂ¾·¯©£¢¤¥¦ª®°´·¸¸¹¸µ°«¥£¨¦œ™©´·¹¹·³ªš€‡œ¨¬­°´´¶·²¨ ¤©¯ºÀÀ¼°Á´¯°°±µ»¾¿ÀÀ½»¹¸¹»º·¶³°«¨¤ ž ¤«²·µ­¨¦¬¸™Œž¢¥©«ª¨®¸»¼¾¾¼¹»»º»½ÀÃÅÇÊÍÎÍÎÏÍÊÉËÍÊÌÌÉÉÊÊÊÇÇÇÈÊËËËÊÈÉÉËËÌÍËÊÉÉÇÅÄÃÅÄÃÄÄÄ¿¾¿¿À¾¾¿¿¿¾¾¾¾½»¹ºº¸¹¸¸¹¸¶¶¶¶¶µ±¹s2’·°¯­¬«©¦¤¦—G#L—¥£¤¤¤¡Ÿžœt g¤›œœŸi1t¯¨³o0”¼º½¿ÀÁÁÁÁÁÀ¿º´°ª¦ œ¢©®±´·¹»¿¿ÀÁÀÁÁ¿»º»¼¼½¾¿¾½½¾¾¼»½ÁÄÆÊÍÐÓÓÕÖÒÎËÇþº²ª¥¢£¦¦¨¬°²¶¸¹¹¹¶´®ª¢ ¤ —°µ¹¹·¶°§Ž‡”¢«­­°³¶·¶°¤Ÿ¤¨°»ÁÁ¹±Éº±°±°²µº¼¾¿¿¼¸··¹¸¸¶µ²¯©¥žšš¡§±¸µ¬¨§¯°‘›¢¥§ª«¦¬¸¼¿¿¾½»»¹º»¼¾ÁÄÇÊÌÍÍÏÏËÉÈÈÊËËËÊÉËÊÉÈÆÈÉÉÊÌËÉÉÊÉÊËËËÊÊÊÊÈÆÆÄÄÄÄÄľ¾À¿¿¿¿ÀÀ¿¾¼¾¾¼ººº¹¹¹¶¶¹¹·µµ¶µ³±¸j.—´°¯®­«¨¥¥›B!)#JŸ¤¥¥£ žŸž n"r¤£zOXw±¬²Z;¬½»½¿ÀÂÁÁÂÁ¿»·²¬§¢Ÿœž¤¬¯³··º½ÀÀÀÀÁÁÁ¼»¼¼»½½½½½½½½½¼¼¼ÀÄÇÊÍÑÔÔÕÓÏÊÉÄÀ¼µ®¥¡¡£¦§©«¯²µ¹º»¸¶³¬¥ Ÿ œ™¨¶¹¹¶´±¬‚‚Œ™¥©¬®²¶·¹´­£ £«´¾À¿¸¯Ïµ²±°±´·¹º¾¿½¹¸·µ´¶¶µ³²®¨¡™—˜š£¯¹¹¯¬­¶¢•££¦ªª¨¬¸¼½¾½¼½º¹»¼½¾ÁÄÇËÌÎÐÑÏÌÉÊÉÊËÊËËËÉÈÉÈÇÈÉÊËÍÌÉÊÊÉÊÉÉÉÈÈÉÉÊÉÇÅÄÃÄÄÃÁ¿¾¾¾¿ÀÁÀ¿À¿¿¾¾¼»»»ºº¹¹·¸»»·¶¶µ³²±¶b/𴝱°°­©§¥M'()%dª¤¤£ ¡¡Ÿ¢j"}¥ž¤‹EŒlwµ²¥?`½½¿ÀÂÃÂÁÀ¿¼¸´­©¦ žž¢¨¬²µ¸¹»¾ÁÂÁÀÀÀ¾»»½¾¼½½½½¼¼¼»»º½½ÀÃÈÌÎÒÓÔÓÐÍÈÅÿ¹±¬¥¡ £¥¦©¬°´¶¹º¹¹¶´¯¨Ÿœœ˜°¹»¹·´¯¥Œ‚ˆ‘ž¤©¬¯´¹¸¸´©¢Ÿ¤®·¾Á¾¹°ÏȺµ´²´µµµ¸¹»»º¸¶µ´³³µ²³°«¦ ™–““›¡¨µ»·³¶¶› ¢¥¨©¦ªµº¼¾½»»º¹º½¿ÂÃÅÉÍÎÎÐÐÎÌÊÊÊÉÉËÌÍÍËÉÊÈÈÈÇÉËÌËËËËÌËÊÉÈÈÉÊÈÈÉÇÆÃÁÁÂÁÁ¾½¾¾¿ÀÁÁÁÁÀÀ¾½¼»¹¸¸¹¹¹º»ºº·µ¶µ³³³·^4³®°°°¯ª¬u%&&'&/•¥£¤££ Ÿ¥e'…¥¦•IEzXy´·|5•ļ¿ÀÂÃÃÃÂÀ¾º´®©¤¡ŸŸ¢§¬²¶¸»¼½ÀÃÄÂÀÀ¿½º»½¾¾¿¾½½¼¼¼¼»»½¿¿ÃÊÎÐÓÓÒÐÍÈÅÃÀº³®¨¥¡¡£¤¥©¬±¸º»»¹¹¶³­¥›š™£³º»¹·³«œ‚‚‹–¢¦¨­±¶·¹¸±¤¢Ÿ¥°¹¿Á¿»°ÐÍÀ·¶¶¶´µ¶¶·¸¹··µ´µ³²²³´³°«¥Ž’–›¥³½¿¾¹¦› £¤§§©´¹»¼¾¼»¼¼»¼¿ÃÄÅÊÍÎÏÐÐÏÍËËÊÉÉËÍÎÍËÊÊÈÈÈÈÇÈÉÊÌÌËÌËËËÊÈÉÉÈÇÇÇÅÄÂÂÁÁÀ¾¾¾ÀÁÀÀ¿¿ÀÀÁÀÀ¿¼¹¹ººººº»º¸¶´¶µ´´´¸[9¡±®¯¯°®«¥B%)'&'++y¬¤¥¤£¢Ÿ¥^-§¦dm…g@}¸µPJ¹ÁÀÁÂÃÃÃÄÂÁ½µ°ª¦¢¡Ÿ¡¥ª®´·º¼¾ÀÃÄÄÃÁÁ¿¼»¼¾¿¿ÀÀ¾½½¾½½½»»½ÁÅËÏÑÔÔÑÏÊÆÃ¿»´°«¥¢¡¢¤§§¨«²·ºººº¸µ²«£›™—™©¶¹º¸³¬¦“…™¤§ª¯³¶¸º·¯¦¡¢©³º¿ÀÀ»¯ÑÏĸ··¸·¶µ´´¶¶µ³³³²³³²²²´³¯¦¢”‰‰Š‘•ž­½Æ¿«˜ž¡£¥¤§²·º»½¾¼½½¼½¿ÂÅÇÉÌÍÏÐÑÎËËÊÉÊÊËÌÌÊÊÊÊÊËÊÊÉÈÉËÍÍËËÌËÌËÉÈÉÊÈÈÇÅÄÂÂÁÁÁÀ¿¿ÂÃÂÂÁÁÁ¿ÁÁÁ¿»¹º¼»»º¹¹·µ´¶·¶´´´¶X>£¯®®®¯¯­Ÿ9,+)))0.v¬¦§§¥£¢§Y2–¨¢—®±µq‚½Ÿ8}ÇÂÁÂÃÄÄÄÃÁ½¸±­©¦¢ Ÿ¥©¬°³·¹»¿ÃÃÅÃÂÀ¾½»»½¾¿ÁÀ¿¾¼¾¿¿½½¼¾¿ÄÉÍÏÒÓÒÐÎÈÄÁ¼¶±¬§£ ¡¤¦§§©ª²·¸º¼¹·´¯ª¢™–“ž±ºº¹µ±«œ„‚‡’ž¦§¬²¶¶¸¸µ¬¤œ£­µ¼ÀÀ¾¹°ÒÒʺ¹¸»¹·´³³³²²¯¯±³³´³±°±³²¬§ –Іˆ‰“•Ÿ­´«—™ ££¢¥¯¶¹»½½½½¾¿ÀÀÁÅÈÉÊÌÎÎÏÌËÊËÊËËËÊÊÈÉÈÉÊËÌÊÈÇÉËÍÎÍËÌËËÊÉÇÇÉÉÈÇÅÅÅÃÁÁÂÀ¾¿ÂÄÃÃÂÁÀÁÁÁÁ¼»ºº»½»¹¸¸µµ···¶´µ¶µQB©¯­«¬­­¬ªZ04*+57F—§¦§¦¥¥¤§T6­ª®­­³”›½lA®ÃÀÀÁÂÃÃÂÁ¾¹´­¨¤¢ Ÿ¢¦«¯²´¶¹¼¿ÂÄÄÃÀ¿¼¼»¼½½¾¾½½¼»¼½¿½¼½ÀÄÇÌÏÑÑÐÏÎËÈþ¹³®©¥¡ ¢¤¦¦§ª­³·º»º¸¶²®¨¡™“•§¶»»¹³­¨’€†‹š¢§«°µ··¹¸²§š¢°¹¾¿À½¸®ÏÒξ¸º»º¶µ´³²³±­¬®±±³²°¯®®±¯¬©Ÿ‘†„‡ŠŒ•›—–ž££¢£­¶º»¾¼¼¼¼¿ÀÀÂÄÈÊËËÍÎÎÌÉÇÉÊÉÊÉÊÊÉÉÊÊÉÊÌÌÈÈÉÊÌËÍÍËÊËÊÈÈÈÇÉÈÆÅÅÄÂÂÁÂÁ¿ÀÀÃÃÃÂÁÁÀ¿¾¿¿¿¾½½½¼ºº¹··¸¹¹¸¶¶µ²LI®®¬¬­­­ª®œme=1bm¥£¦©¨¦¤¤¦O<¤¯¯­¯±²³·ªAjÆ¿ÀÀÁÂÂÂÁ¿¼·±«§¢¡ŸŸ¡§­°³¶¸º½ÀÁÂÂÀ½½»ºº¼¾¾½»º¹ºº»»¼½½¾ÂÅÉÌÏÑÒÏÍÍÊǽ¶¯ª¦¢ ¡¢¤¨¨¨«°µ¹º»º¸µ±¬¦ ˜“¯¸¸¸µ±©Ÿ…‚Š–Ÿ¦©­±¶··¸µ©Ÿ˜ž¦°·¾Á¾·¬ÏÐÏź¼»ºº·¶³²²°«©ª­®®¯®­«¬®°®¬§‹‚ƒ…„‡”–‘œž¡ ¡¨³¸»¼»¼½½¿¿¿ÁÅÇËÍÎÎÍÍÊÊÈÉÉÊËÊÉÉÊËÊÊÊÊÌÌÊÉÉÊËËÌÍÌÊÊÊÉÈÈÈÉÈÆÃÂÁÁÁÁÁÀÀÁÀÂÁÁÁÀÁÁ¿¾¿ÀÁ¿½½½½»»»º¹·¹¹¹·µ²¯FO°®­®°¯¯­¬°³¤H7–­£ ¥¥§§¥£¦ªLB«±±°±²³µ½‡:›Ä¾ÂÃÂÃÄþºµ°ª¦¢¡ ¢¦«°³¶·º¼¿ÀÀÁ¿½¼¼¹·¹»»º»¸·¶¸¹º»»¼½¿ÄÇÊÌÎÐÑÐÍËÈÄÀ¸±­¨£ŸŸ¡¢¥¨§¨¬´¸ºº¼»¸µ±¬¦ž””¦µ¹·µ²¬¤“‚…–¡¨¬¯µ···µ®¤œš ¨®¹¾Â¼µ«ÎÒÐȼ¼»ºº¹·¶µ³®©¤¢¤©«««ªªª­¯¯­ª£—‡€‚ƒƒ‡Œ‘Œ”›œž¨²·»½¼»»½¿¿ÀÂÅÇËÎÐÐÏÍËËËËÊÊÊÉÉÉÉËÊÉÈÊÍÍËÉÊÊÉÊÌÍÌÌÉÉÉÈÈÇÈÉÅÃÂÁÁÁÁÀ¿¿¿À¿¿¿¿ÀÁÁÀ¿¿¿¾½¼½½½¼¼¼»¼·µµ´³²±§;Q²¬­®¯­¬¬­¬®ˆ>8}§ £¤¦¨¥¥§ª®IC¯²²±²´¶¸¸XV½À¿ÂÂÃÃÅÂÀ¼µ°­¨¥¢¢¡¤ª­±´·¹¼¾¿¿ÀÀ¿¾¾»ººº»ºº¹¸¹··¹ºº¹»»¾ÂÆÊÍÏÑÐÐÍÉÄÁ»µ±«§¡žž ¡£¦§¨¬³¶¹¹»¹¸¶²«¤ž–¬¶¸¶³°¨ž‡…†š ¦¬´¹¹·¶³¬ ššŸ©±º¿À¿¼´ªÏÏÏÊ¿»¼»¹¸¹·µ²­© Ÿ £§©¨¨¨«¬®°±¬§ ’‚}|€„‰ˆ™™™›§²·»½½½¼½¿ÀÂÃÄÇÈÊÍÑÏÌÌÌÌÌÍÍÌÊÉÉÊÉÉÉÆÈÊËËÈÊÊÊËËÍÌÌËÊÉÈÈÈÉÈÆÄÃÂÂÁÂÁÀ¿¿¾½¾¾¿¿ÁÁÀ¿¿¿¾½½½¿¿¿À¾½¼¸·¶´²±±¥7W¶®¯°±¯­­®®¯—|z¦¢£¥¦§¥¨«®¯GJ³²³³µ¶¶¼›;†ÈÀÂÃÅÄÃÿ»²¬¨¥¤£¢¤©¬¯´·¹º½¾¾¿À¿¾¿¿¼¼»¼¼¼º¹¸¹¹¹º»¼ºº½¿ÃÇÊÍÐÑÑÎËÅÁ½µ°­¨¥ Ÿ ¢¢¢¤¦©®´¶¹ººº¸µ°©£›¡´¸¹µ±­¥‘„Д𠦱·¸¹¸¶±¦ž››¡«³¼¿¿¾¼´¨ÎÍÍ˺¼¼¼¸¶µµ²¬¨¢  Ÿ ¡¡ ¤¨«®°®¬© {|~€†Œˆ‰–™–—¢°¶¹¼¼¼¼¼½¿ÁÃÄÆÈÊËÍÏÍËÌÌÌÍËËËÈÈÈÈÇÈÇÉÊÉÉÉÊÉÉÊÌÍËÊËÊÈÉÊÊÊÉÈÄÄÃÃÃÃÂÀÀÀÁÀÁÁ¿¾ÀÀÁÀ¾ÀÁÀ¿¿¾¾¿À¾¾¼¹¹¸µ´´µ¤5c¹®°±²¯­®°°°²µ²ª¥¥¦§©¨§©¬±°EP¶²²³¶¶·¿oE³ÅÂÃÄÆÆÅľ·¯ª¥£¡£¥¦«°²µ·¹¼¾¿ÀÁ¿¾¿¾¾»»»»»»¹¸¹º¹º½¾½¾ÀÂÅÇÈËÎÑÒÏÌÉÄ¿º±¬©¦¤ ŸŸ¡¢¢£¤©¯³·¸¸¸·µ³­§¡™˜«´¶´²®¦œƒŒ—¤«·¸¹·¸´ª¢™šž¥®¹¾¿¾¾¹²¨ÑÐÒ̹¼¼¼¹·µ´°«©¥¡¡—•——›ž£¦ª­®°±¬¢‹~zz|}€ˆ‡…“”““Ÿ®µ¹º»½½¼»¼¿ÁÂÅÈÊÌÍÍÌËÌÌÌÌËÌËÈÇÈÇÆÈÇÉÊÊÊÊÉÈÉÈÉÊÊÉÉÉÉÉÉÊÊÉÈÇÄÄÄÄÃÂÀ¿ÀÁÂÀ¿¿¾ÀÀ¿¾¿¿ÀÀÁ¿½¾¿¾½¾»¹¹¸¶´µ¶ 6p¼ªª³²¯¬­°°°²¯­©§¨©©©¨¦§«²¯AWº³²´µµº²DlÇÁÂÃÅÇÆÅÃÀ»´¯ª¦¥¤¤§©­±´¶¸¹¼¾¿ÀÀ¿¾¿¾»¸¹º»»ººº»¼¼¼¿¿¿ÂÆÇÈÉÉÌÐÔÒÏÌÇÁ»µ°«§£¡Ÿž   ¢¢¦«°³µ·¸¸µ´²­§ šž®²²±±©¢{‚„Œ”¦²·¸¸¸¶±§žšœŸ¨µ½¿¿½»¸±¦×ÔÓÏȼ¼»··¶´²°¬©¥¢¡œ”Ž”™œ¡£¦©¯°¯® ‹yyy{|~„’ž¬±µ¹º½½¼¼¾¿ÀÂÅÈÊÏÏÎÍËËËËÌÌÍËÉÈÇÆÆÆÈÉÉÉÉÉÉÊÉÈÉËÌËÊÉÉÉÉÉÊÉÇÇÆÇÆÅÅÃÁÀ¿ÁÀÀ¿¾¿ÁÁÀÀÀÀÀ¿À¿¿ÀÀ¿¿¿º¹¸¸·¶¶¹š2yÁ™m¹³²}£±¯°°°®«¬«¬ª©¨§ª¯³­<^¸²²µ¶¶Â8¢ÈÄÄÆÇÇÅÃÁ½¶°¬©§§§§«¯²¶¹ºº¼¿ÀÀÀ¾¾½¾¼»¹º»»»»¼¼½¾¾¿ÂÁÂÅÇÈÊÌÍÐÑÒÐËǽ·±®«¨¤¡ŸŸ Ÿ ¢¤§¬°³´··¶´²°¬§¡œ¥®°°°¬¥™…€ƒ…Ž˜¢®¶¹¸¸ºµ¬¤œœ¡ª¸¿¿À¿»¶®¥ÚÖÓÐʽº¼ºµ´²°®­©¦£¢œ•Љ‹‹“•–ž £§©¬ª§œ†{yz{z~|‡Žª¯´¹¹¼¼½¾¾ÀÁÁÅÊÍÐÒÑÎÌËËÊÊÊÉÉÇÇÆÅÆÇÇÉÈÉÉÈÉÊÉÉÊËÌËÊÉÉÈÈÉÉÈÇÆÆÆÆÆÅÂÁÁÀÀ¿¿À¿ÀÁÀÀÀ¿ÁÀÀÀ¿¿¿ÀÂÁ¿»º¹º¹¸¸¼”.|Á²Ro‚wc²±±²²°®¯¯®®«¬©§®³¶ª;c¶±³·¹½Á]ZÈÅÆÇÈÇÆÂÀ¾º³­©©¨§§«®²¶·¼»¼¿ÁÀÀ¿¾¾½¼»»»»¼¼¼¼¼½¾½¾ÂÄÃÃÅÇÉËÌÎÎÏÏÌÇÁ½·±®«©§¢ž ¥¥¨­±´´´¶µ³²¯«¨¡¦­®®¬¦¢€„†Œ•Ÿ©²¸¹¹¹·¯¥Ÿœ›œ¤®¹¾ÀÀÀ»´«¤ÜÙÓÎÈÀ¹º¹·´±¯­«©¦¤¤ ˜‹ˆ‡ŠŽ“——›Ÿ¢¡¤¢•}yyyxywŠŒŽ™©¯´·¸¹º½¿ÀÂÂÂÆÉÍÐÑÑÏÎÌËÌÊÉÉÈÇÆÆÇÇÈÇÉÈÉÉÊÊÊÉÉÊÌÌËÊÊÊÉÉÉÉÈÇÅÅÅÆÆÅÂÁÂÂÁÁÁÁÂÂÂÁÀÀÂÂÁ¿¿¿ÀÁÁÁÁ¿»»»»ºº¸½“.ƒ¿¼iPsBй²²³²±°±±¯­««©«±³µ¦7f¸²¶¹¼Ä¨A“ÌÄÆÆÆÅÄÁ¾º¶°©§§¨©ª¯±´µ·»»¼¾¾¿Á¿¾¾¼»ºº¹»¼¼¼»º»¼½¾ÁÃÃÄÆÇÈÊÊÌÍÎÎËÆÀ»µ°¬©§£ œ›œž¢¥¨¬¯²³³³´³³±­«¦¡Ÿ§«¬­©£™…€ƒŠ‘›¦¯µ¹¹¸·°¨¡œš›ž§³»ÀÀ¿¾»´ª£àÝÖÍÆÀ¹¶µµ³¯­«ª§¥¤¥¢˜††…ˆ‰ˆŽ’˜—Œ|yyxvsz†ˆ–§®±µ¶¸¼½¾ÁÃÄÅÇÊÏÒÐÏÏÎÍÎÎÌËÌÌËÊÇÇÉÉÈÈÇÇÉÉÉÉÈÇÉÊËÊÊÊÉÉÉÉÉÈÅÅÆÅÄÄÄÁÁÁÁÁÁÁÂÂÃÃÂÂÃÃÃÂÁ¿¿¿À¿¿½¼»¼¼¼»»¸¾Ž/ˆ»¸dž_ª³³²±¯°±²³²¯­««®²³µ¡2h¸²·½¾ÄpRÀÈÅÃÅÆÃÁ¾¾º³®©¦§¨ª­±²µ¶·º½¾¾½¼½¼¼¼ºº¹¹¹º»º¹º»º½¿ÀÂÃÄÅÆÉÊÊËËÍÍÊÇý·±¯¬¨¥¢ž›››œŸ£¦ª®°±±²²²±²±®«¦Ÿ §©ª«¥Ÿ‚…Œ”¡ª²·¹¸¸³¬§Ÿœ›œ¡ª·½À¿½¼¹±©£ÚÙÔËĽ¸¶¶´²°®¬«¦¢ ¤¤ š“‰„ƒ…ˆˆ‡‡‡‚€~‡‹Ž„{yxrlxƒ„Š”¦¬­°´º¼¼¾ÀÂÃÆÉÌÏÒÒÐÏÏÏÎÎÍÍÍÌÌÊÈÇÆÇÈÈÇÆÇÉÉÈÇÇÈÈÈÉÊÊÉÈÉÈÉÈÇÆÆÆÄÄÃÀÀÁÁÁÁÁÂÂÃÄÃÄÄÅÄÄÂÀ¿¿ÀÀ¼»»»»»»¹¹·»Š.ޏµªQa~·±²²°®¯°²³³²°°¯²´´±˜0l¸´º¾Â°EˆÍÄÅÃÅÆÃÀ¾»·²­ª©©ª¬¯²µ·¹»¼½½¾¾¼»»º»»º»¹º¼¼½»»½¼¾ÁÃÄÅÆÇÈÊËÌËÌÌÊÇÿ¸³­©§¦¤ šš›œ¡¥¦ª®¯°²³²±¯°¯­©§Ÿ §¨¨¨¡˜…ƒ‰’œ¤®¶¸·¸¶°«¥ž››ž¦°¸¾ÁÁ½¼·¯¦£ÉÉž»¸¶·´±¯­­ª¦¡¡¢¢¡œ–Œƒ€ƒ…††„€{yxwx~€|vvtlv„‚†”¤««°µº¼½¾¿ÀÂÆËÏÐÒÓÑÏÎÏÎÎÎÍÌÊÉÉÈÈÆÆÇÇÇÈÉÉÉÈÇÈÉÊÉÉÊÈÈÈÇÈÇÆÆÆÆÆÄÅÁÀÀÁÁÁÁÁÃÃÃÃÃÃÅÄÅÄÁ¿¿½¾¾»»»»»»º¹º·¼ˆ-¹¯µ_@¨µ³²²°¯±±²³³³³´µµµ²²+r¸¸¾¾ÇzL¸ÅÃÄÅÅÄÿ¼¸µ²®ª«¬¬¯²µ¹º¼½½¿¿¿¿¿¾¾¼º»½½¼¾¿¿¿½½½¾¿ÂÄÆÇÈÉÊËÌËËËËÉÅÁ¼µ¯©¨¤¢Ÿ›˜™ž¡£¥¨¬¯±±±²°¯­­«©¤ž ¤§§¤Œ‡Œ˜¡«µ¸···´°© š™› ©²º¾¿¿¿¼´ª¤£¼»¹¹¹¼»µµ²°®¬¬ª¦¢¢ žš•†€€ƒ…ƒ€{wtstwvwvtrojq‚ƒ‚¡ªª®´·»¾ÀÀ¿ÂÅÊÎÐÑÑÎÏÎÍÍÍÌÌÌËÉÈÇÆÇÅÆÈÈÉËÊÊÉÇÈÊËÌÊËËÊÉÇÆÅÅÅÅÅÅÅÄÁ¿¾ÀÁÁÁÂÂÁÁÁÁÂÀÁÂÁ¿¾¾½¼½½¼»¹¹»º¹¸¶¹‚-й«­†t³®­®°¯¯°¯±²³³²´¶´³²´Š,~À»¿ÀµItÇÁÅÆÇÆÃÀ¼º¹´°¬ªª«­±´¸¹¼¿¿¿ÁÂÁÀ¿ÁÀ¿½½¿¾¿ÁÀÀ¿¾¾¾ÀÂÅÅÇÉÊËËÌÌËÉÈÇÆÄ¾¹±«©¥¢Ÿœš˜šŸ¡¤¦¦©¬®®¯±±°°®¬¬«£ž¢¦¤ –†€ƒ‰“œ¦°µ¸µ·¶³ª¢œšš¥­·¼¿¿¿½º±¥££³±³³µ·º·°°¯­««©¦¡ Ÿœš˜—”Œ€‚}ytrrrrqoqpmifm‚‚‹Ÿ¬©«±·¼¾¿ÀÁÄÅÈËÎÐÏÏÎÌËÌËÊÊËÉÆÅÆÇÆÆÇÈÈÈÊÊÉÈÈÈÉÊËÌËËÊÊÈÆÇÇÆÅÅÅÇÅÂÀ½¾ÁÂÁÂÂÁÀÂÁÁÀÁÁ¿¾¼½½¼½½»º¹¹»¹¸¶³´’.W§«©¬±°®­­°°®¯±²³´²±±±³²²¼‚2–Á»½Å–:£ÈÅÆÅÅÅþ»¹¸´°­ª©ª­±µ¹¼¾ÁÁÂÃÂÃÄÄÄÄÃÃÃÁÀÁÂÁÀ¾¿ÀÁÄÅÇÆÇÈÊÌËËËÊÇÆÄ¿¹²¬¨§¤¡š˜™›œŸ¢£§ª­®®®¯²²°°®««§£››Ÿ¢¡™‚‡™¡¬´¶¶¸¶³­¤žœ›ž¨²¹½ÀÀ¿¼·®¤¢¢®®¯°²µ¸·±­­«ª©©¥Ÿœžœ˜““„}€}vtrppnkkmkihch{€ƒŠ›«««¯´¹¼¿ÀÃÅÇÊËÍÏÐÏÌÍÌÊÉÉÈÉÈÅÅÅÄÅÇÈÈÇÈÊËÉÇÇÈÉÉÊËÌËÊÉÇÉÈÈÈÇÅÆÆÅÂÁ¿¿ÁÂÂÁÁÁÁÁÂÃÁÁ¾½½¼½½¾¼»ºº¸º»¹¸¶´²¯T'BWcltz‡”› ¥§©©®¯°¯¬¯®® RI³À½½ÆmLÀÅÇÆÅÄÿ¼º·µ²®«ª­¯²¶»¾ÁÄÄÅÇÅÅÇÇÆÇÇÆÅÃÁÂÅÃÁÁÂÂÄÅÅÆÆÈÇÉÊËÊÉÈÅÅÁ¾º´®¨¥¦¢Ÿš™˜šœ £¤¨¬®¯°¯®°±°°®¬©§¢™œ ¡›ƒ…‡’¦²·¶·¶³®¦¡ž››¢¬´»¾ÀÀ¾º´©¢¢¥®®®®¯²µ¸´­«ªª¨¥ ™š››˜“‡€}~~|zxuromkjihggeaev~‚Š›¥§ª­±µº¿ÀÃÅÉÌÎÐÑÑÎÌÎÍËÊÉÉÈÇÅÄÄÄÄÆÇÉÈÆÉÈÆÆÇÈËËËÊËÌËÊÈÊÉÉÉÈÇÇÇÇÃÂÁÁÂÂÂÂÁÀÀÂÂÂÀ¿¼½½½¾¿¿¾ººº·ººº»¹µ²µ¦eD;7630*0348;X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¥ª¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdЧ¶¸¹¹º»½ÀÂÂÆ¯EŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥§¬»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£¨¯»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:?:–ÖÌÍÍÍÌÍÊÊÒºl>VŸ¢†IFHLMKMLOT]ds‹‹‘—š›Ÿ¢¥§ª¬¯±´¶¸¹··¸´¯¬¨¥£¡ žœšššš›››œœžž¡£¤¦¦¦¨©©¨§§¦¦¥¥¥¤¢Ÿ›’|{¡ª©ª««¬¬«ª««­­­­¯¯¯°²·¹¸·³­¨¥¡¡¢¦©©©«««™Š†„}z{}~€ztqppqrrsvuxxz~€‚‚ˆ•˜›œž  žŸ™‚s_RQaq~Œ”˜›—’“•––˜™ž£¤¤¥¥££¥¥¦¥§«­°²µ¸¹»»»¼¿¿ÁÃÄÅÆÆÇÇÇÈÌÏÒÒÑÐÏÎÏÏÐÐÏц;C>>>¦ÐËËËËÊÊÎÊ•E-:<=9QžÎÈÁÆi'..4>IVfs”˜››’‹‰¹ÄÃÉ—/*'&(&'$"!!6›š™¡a!9q•›œž £¥§«­®²µ·¹»º¹¸µ°«¨¦£¡ŸŸ›››œœœ›žŸ¡£¤¥¥¥¦¥¦©§¥¥¦¥¤£¤ Ÿœ˜ƒ™©¨©ª¬«ª«ªªª©©«««ª¬­­°³³²¯«©¦£ŸŸ¢¥¦¨©©««•‹‡ƒ|xxz{}~~ytpopppqrsttvwwx{}}~‚ˆ††‡ˆ‰€vfVO[n‡’–—™–‘ŽŽ””••”–šžžŸŸž ¡¢¢¢£¦¨ª­°²¶¸¸¹º¼¾ÁÂÄÅÅÆÇÆÆÈËÏÑÑÐÑÏÏÏÏÏÎÍÐw:DA?@­ÎÊÊÊÉÌÒ¯c.3<=?:h¸ÑÉÄÀÄc243310.--/1/1552215¨ÇÂɈ140.,*(''&#""!!  9˜™–›Z " EŽª£§©«®¯±²³·¸¸¹¸·µ°­¨¥£¡¡Ÿš™šš›œš›œœŸ¡¤¤¥§¨¨§§¦¥¥£££¤¦¥¥¥¡Ÿš•€’¦¥§©ª«ª¨¨§¨¦¤¦§§¦¦¥¨ª­­¬ª¨©¨£žœœŸ£¦¨©ªªªš—†€~|ywxyy{}{uqmlmmmnooppopqsrsvvrutqqqrrnnj_UKTl‡””•––’ŽŽ‘’’’”——™™›  ¡£¥¥¦¨©¬­¯³µ·º¼¿ÁÅÅÄÅÆÈÉÊÍÐÑÑÏÐÎÎÏÎÎÌËËk;B@=C´ÌÈÊËÑË„80:=>9B…ÈÑÇÆÃÀÆ^43225689:9877778:<@®ÄÂÊ~.1/+*)'%$#!"! "! @™˜™N!#$%-†¯ª­°±³µ´µ¶¹¹¹¸¶²­©¦¢¡¡žœšš›š››™—˜™œ ¢¥§¨©¨§§§§¦¦¤¢£¤¥¤¤¤ ™˜¡¤¦§©©¦¡ ž™––™ ¡¢£¢£¤¦¨§§§§¨¦¡œ™šž¡£§¨¨©©‘“‘ˆ€|zzwuuvxy{xsmjiiiiikjllijjjijljhggfeeecb]WMIMe{ˆ˜•““‘‘ŽŽ‘Ž‹’••–™šœž ¢¢¤¢¢¥¨ª­°²´·º¼¿ÁÂÃÅÆÈÉÌÐÐÑÑÏÎÌÍÎÍÌÊËÇ[DC@9I»ÊÅÅγc.4=@<9V£ÐÍÆÇÇÃÁÃU.123242024467:9797=¯ÀÁÉs.1-*%*71/.,)(#" HŸ™”’C(%#"!%$&($.”³°³´µ·¶¸¹º»»¹µ°«§¥£¢¡ž›š››œœœ›˜˜™ ¢¤§¨©©§§¦¤¤¥£¢££¢¢¢¢Ÿ›š—’™¡¤¥¦¥¢œ—‘‰€~€‡˜žŸ¢£¡¡¦©©¨¥ ›˜˜›¢¥¤¤¦¦§_ˆ‡~zyxurstwwxtlifeecccedbcc`b``ac``_^^\ZYXTPF@I_x„˜—”’‘”’Ž‹ŽŒ’’’”•™›ž ¢¡   ¡¤¦ª¬®²µ¶¶¹¾ÀÃÄÄÆÇÊÏÐÑÐÎÍÌÌÌÌÉÈʽL<@@8SÀÃÃÈ”D3::;:;t½ÎÇÆÅÅÄÁÀ½K034.B€yl\TJ:788669Z·½¾Åi10++&v “•“Œ…{tmffdx•“ŽŽ@(x†‚~vY3"$(*-&A«·¶·¸¸¹»»ºº¹¶±­©¨¥¤£¡ žœœœžžžœ›šœŸ¢£¦¨ª«ª¨¦¥¤£¢¡¡¤£¢¡ Ÿœš—”“𡣦¤Ÿ™‡ƒyuuu‡’™›œ ¤££¨«¬«¨£Ÿš–™Ÿ¡¡ £¤£¢4^†……‚}{zwtqpqsutmifa`^^][XY[WVTWWWXYXYWUSQOMID;34]w‹“——”’’‘ŽŒŽŒŠŒŒŒ‘’’•–—™Ÿ¡¡Ÿ ž ¡¡£§ª®±³µ¸»¼¿ÃÅÅÄÇÌÎÏÎÍÌËËÊÈÇÆÈ³@48:6U½Â¾s24<881DËÇÀÀÂÁÁÀÀÀ·B256/_ËÇÉÄÀ·¦—”˜©¸¾½¿Äb)-+++‘²©§§¥¢¡Ÿ¡¡žžœ˜“‹47 ¡¤§«®›Q&*,,.'{¾¹»º¸º¼»»¹·²°«¥¦¤¥£¡¡žžž ŸŸŸœœž ¤¥§©ª«ª¨§¦§¦£¡¡£  ¡ œœš–”•› ¥¦£Ÿ•Šytrtv}†˜œ¡¦©ª¬±²±®ª¦£Ÿ¢¥¤£¢¢¢¡ž@5`†ƒ|{vwurooqrtqifc`^\XVURQPMNOOPRRPPOLKID@7,!T}‚ˆ•–”“‘’’‘‰‡‡Š‘“”–˜—šœœžžžž ¤¨«°µ·º¼¾ÂÄÆÆÈÊÍÎÌËÊÈÇÅÃÂÂÄ©93540O¾°V-68760S¤ÆÁ¾½½¾¾¾¿Àó?3451jÈ¿ÀÂÃÃÄÅÌÎÉÆÃÁ¿À¿Â_,.,()’«¤¡Ÿž›š›˜˜˜—•“,= ¡£¥§³«P(..0,O¶»»»»½¼ºº¸µ²¯«¥¤££¡ ¡œžŸ  ŸœœœŸ¤¨¦¦§©ª©§¨¦¦¤¡  ¡ Ÿžœ›™˜–“”›¡¥£š“‡{sqnnpuz‡’›¡¥¨ª®°±¯®ª§¤Ÿ¡¢£¥§§¥¥£ŸD@<^ƒ~~{xwutppprrqoida][YVSPPMHFECDIHHGEB@:0!P|„‡Ž“•–•“”––”‘ŽŒŠ‡†ˆŽ‘“–––˜™™™˜™š››œž ¤¨«®´·¹¿ÃÅÅÆÈÌÌËËÆÃÀ½ºº¹¸»™-./.(LŽA&53350d²Ãº¼¼»»¼¼¼¾¾Á¬>4352nÆ¿ÀÀ¿ÀÀÃÄÃÂÀ¿À¿¿½½X++)&*‘¦¡Ÿœ›————˜š™”’†'F¥Ÿ£¥¨©«¸œ6,/12<¤À¼½¼½¾¾¼¹¶³®©¥£¢ ŸŸžžžžŸŸŸŸŸ›œ¢¨¨¨¨©ª¨¨§¦¦¥¢ Ÿ ¡Ÿœš—•–”’”šš‘ƒ{qnnlmmptx{ƒŽ˜ ¡£§ª««ª¥£¢ žœžŸ¢¥¤¤£¢¢DCA8f||{zutsqnnnmnnjf`][XVRQMKHD>;99<=:3+' Hy‚†Š“”””•””“‘ŽŽ‹‰ˆ‡ˆ‘’‘’”“’”––——˜™š›œž¤§ª®°¶¼¿ÀÂÆÈÌÌÉÆ¿¸²®®®¯°µŽ%)'()+."-.-,1l·º´¶¸¸¹»¼½½½½À§84493qĽ¿ÁÀ¿¾¿ÁÁÂÀ¿¿¾½»ºQ%'%#'ŠŸ›™šœ›–•—™˜˜•‘Ž€#M§¢§©«¬®²·Q*//34“Á¼¾¾¿¿À¾¹¶°ª¦¤£¢ žžŸ   Ÿž ¥¨§©ªªªª¨¥¥¤¢¢ŸŸ Ÿœš™˜•“‘”—“…}tllmjmmptsux‰“™™œ £¢¢›‘‘—œ›™š› ¢¢¢¢¡BCA;=jxwyvtrromllljjhb]ZXUSPMJGB;7542( D|ƒ„ˆ“•–—•’‘ŽŒŠ‹Œ‹ŠŠ‹ŽŽŒŽŽŽ’““““•—˜™˜œž¢¤§ª±¶¸»ÀÃÅÉÊÅ¿µ­¥¡¡¡£¦¯€!&%%%$%))++.y¹¸±²³¶¸¸¸»½»¹ºÀŸ53242tȾ»¼ºº»¾½¼¾¾¼»»º¸ºM%&#"%‡˜–•—–•––“’’”Œ‹Žw\ª¤©­®®²´»_(1263–ýÀÀ¿¼¹·´±¬§¤£¢¡¡ŸžŸ ¢¢¡ Ÿœœž ¤§¨§©«©©¨¦¤¤£¡žž Ÿœ›™˜—•“‘ƒurkijkloqqtstx}†Ž“˜™š˜ƒ||ˆ•˜––˜šž¡¡¢¡¡CCC@7?kuvxurqnlkkjjlhc^ZWTRPMIE@<851( =|‚……ˆŽ”•–•”‘Œ‹Š‡‰Ž‹‰‰ŠŠ‰‰ˆ‰Œ’’‘””“’–™œ £¨®±¶¹¾ÁÄý¶ª¡œ˜˜™›Ÿªp!!!!"$%&&%T¶¯­¯°²³µ···¸¶¶¶½‘/100.R˜¤®·º¼ÀÀ¿½¼»¼¹¸··¶H"$ #‰¢›œ™–’‘”•’‘‘ŠŠŒm e¯§ª¯°±¶¹¿Y/0588¤ÅÁÀ¾½·´²°«¦¡¡¡¢¡Ÿ  ¡¡¢£¢¡ žŸ¢¦§¨§©©§¦¤£££¢Ÿœž ž››š˜–”’މ„tolkiiklnqrsusx~ƒ‰ŒŽ’‘‹€tpqŽ““•–šœžžœ›œCBCA=8>outtrqmmlijhiheaZURRQMID>975(  8x}ƒ„†ŠŠ”•“‘ŽŠŠŠŠ‰ŒŒ‹‰‡†…„„ƒ†‰‹‹ŽŽŽŽ’–›¡¦©­±³¶¹´±© ›—•••˜›¤h#  ""$$3›®©­®¯¯¯¯¯°³´´²»„*--,,'(1>Lau…˜Ÿ¡ §¼¸´±E#!FTSYYZ`dagigkpŠ–c " p±«°³¶¶·¼¬A6346F·ÄÁ¾¼º¶´³®¨¢¡ŸŸŸžžŸ¡¡¢¢¡¢ Ÿ  ¤¤¦¦¥¦§§¦¥¢¢£¢¢ š››˜•”“‹€{vqnkihhkmnqrqtv{‚„‡‰Œ…wpor|‹’’•–™š›š–““AA@@>=5Bqrrtqnmkifffffc]WSROMHB=74* 0r|‚ƒ…ˆˆ’”ŽŒ‹‰‰……‰‹Š‹Š†ƒ€€€ƒ„…†ˆ‰‡ŠŠ‰ˆ‰ˆŠ–˜œŸ¢¦¨««©¥™–”‘”––—ž`5 !";ž§¤¦§¦¥§¨©©­®¬µt$,+)*+(('&$'/028:;5„Áµ±«? @“’“›_ "&"z´¯´·¸¸»Ãl59573tÄ¿¿½»¹¶µ²­¦¡Ÿ ž›œŸ   ¡¢¡ ¡¢¤¤¥¥¥¥¥¥¤¤¢¢¡  žš˜˜š˜“‘Ž‹|trqmlighmlnpqruwz}€ƒ‡ˆytomrx~ˆ‘”˜š›™—‘Šˆ@@@?<=;1Jpprqnmkifcacca`[UUPMFB<9+ 3m|…„†ˆ‰Š‹ŠŒ‹Š…‚„„ƒ‚€€}}}||~~€€‚ƒ€ƒ‚€ƒ‰Ž’–˜™ £¤¤¡œ—•““–––”šQeTM£››Ÿ¡¡¢¥¦¦§§§­e))(*+,+,./25446872‰À³­§>Dš˜š¡^##&$µ°µ¶¸½Å~/9875=®Á½½¼»¹·´°©¤ŸžžœœœœœŸŸŸ ¢¢¡¡£¤£¢¢¤¤¤£¢ žžœœœ™˜˜–—”‘ˆzupljifehkkmooqtwz}~€}yslknv|€†‹”˜››š“Šƒ???>=:75,Nnqppmjgedaa_]^[XQNHB@8$ -  )jx€‚„†ˆŒŒ‹ŒŒŒŒŠˆ„~~|xwz||}|{z{{{z|}~{z{z{|~€ƒˆ‘“–™Ÿ Ÿ™—“‘“–˜•–Io˜2j¡–šœ™ ¢¡£¢£­U%%'$)'%'+,.0214785޼°¨£8 GŸž¨\#&%(&‡Àº¿À¿ªj/7;:82ŠÇ½¾½»¹¶³®©¤Ÿœ›››››œ›œŸžž ¡¢£££¡ ¡¢¤¤£¡ Ÿœ›žŸš••••“‘ŽŽŽ…ytpmhfeeeilmnpswz~}}|zvvnikrx~~‚Š•›ž›–Ž…w?>>=<:85/)Kpllkihfcb_][ZXUOIE@. - %ct{ƒ…†‰‹‹Š‹‹‡…‚€|{zxuwxzywyxvwxwwxxuttuwx||{}€ƒ‡Œ“—›œ—–’Ž‹Œ”–‘Cq™~ x™’”–š››œžŸ¦F!"#)~ŠveTJB:965767’·©£›0K£ ¤¨V$&&)#^ˆƒzs[:+49=<1xƽ¼½»·³°¬¦¢Ÿœšš™š›š›œžŸžŸ¡ŸŸ¡¢ žž¡¢¡    ŸœŸžœš—‘‘“‘Ž‹ŒŠ‚vqmlifdcehkmnsy}yxvvtpghmrx||€Š•šœ•Š‚{w?==;;8641-*Gjkgffdcc_]YVRLLE, - - %_ryƒ„ˆˆŠ‹‰Šˆ‰ˆ„ƒ‚}{zyxvwxwvuvvttttttqorsstwwwww{~…‰•—›š—”ŒŒ’–‘:m‹‘g&„Œ’”•–˜˜™›œ7!! 2¦¸¹»º¸²«¦¢œ™––¨¬¡š+*UTTQONKIHIIKJrª¤¤§N$''))&))%(*/34:6:}Á¿¾¼¼¸³¯«¦¢ž›šš™™š™››œžžž  žžŸŸ ¢¢  Ÿžžœ›œ™–”’’‘ŒŒŠ‹‰spmkidcdfiilqz‚…ƒ}wuusrqkchoqv|€†”›ž—’†xw><;;:54411/):_gfb`ab_ZUMKI:!  - Wrw}‚……‚†‡„‡ˆˆˆ†„€|yzzvwvuutvtssrsrqompqptutwwvwy€‡ŒŽ–›™•“Ž‘Œ6n‡…‰J6Œ’“”—˜—‘.7Ÿ¦«²µºº¼¾½¿¿¾º±¥š“‚%C”‘‘”••—œ£¨§¥¤ªL$((*,-020234213PŸÊÀ¿¾½º³°¬¨¤ œš˜™˜™™™™šš›š›ššššœ›œž ¢¡¡ Ÿž››››š™–•“‘ŽŠ‹‹Š‰‡~vtpnhcbegklqw„ƒxvtqonlhdhnow‡’ ž™ˆƒ{ww><::85434311,1Oa^\\ZVPNL7 - - Lqx|‚ƒ‚‚‚„…ˆŠˆ‡‡‚{{ywwvtuutttrqsrrpooqprrqssttx|†‡–—–“‘‡‡†ˆ‰‰6o~|}‚4G‹ˆŠŽ‘’’”‡%:˜š¢§¬°²³³³´²°¬¥šŒw J‹ˆ‰Œ‘’–—™œ¡§¨ª©¬M&++-/-+.,-1/?b‘ÁÌÂÁ¿»¸±®¬¨¤¡žš˜—˜—–“’“””’‘••”’’–—–™¢¡Ÿš˜—˜˜–“““’‘‹ˆˆˆ‰ˆˆ„}|ysnidbdhmu{ƒˆ„€yuuspolhdefimw…“›¡¡Ÿ™”ƒwuw<;9877655331/.)?ORRQLIA"   - LVas°ÂÉÅÁÁ¿¹·´¯¬©¥ œš•“‘‘ŒŠˆˆŠ‰ŒŽŽ‘Ž‘•–˜˜••–“’‘”“Ž‘’Œˆ‡‡††‡ƒmtxslfcaensy†ˆ†{tpqonkecfghio‚“¡Ÿ›•ˆ‚|xwz;::877442320.--3?A@EG5 - *p{~ƒ„„„……†††††ˆˆ…~€€}{wvtqrrqrnqqqpnmnoppponpqruxz~†Ž’“’‹ˆ„‚„„/"uƒ‚~{€fp‡ƒ……ƒ‡t>‹ˆ‰‰‹ŽŽ‘’Šˆ„€€…a]‡†‹ŒŽ•–™Ÿ¥«­¯±³¶Q-16;<ŸÁÄÉÌÍÏËÅÃÿ¼¶²¯«¨¢™–’Ž‹Šˆ‡ˆ†…„ƒ„„ƒ‚‚…†„ƒƒ„†ŠŒ‹Š‹Œ‹‹‹‹‰†„„ƒƒ„‚aanmjeabhmu|‚„…‚{tqoomlhb`cgiiju„”š—”Žˆ„‚zwz€97788842310/,0;A@;8=4   - $c{€‚„„„…‡††‰‰†…‡…€€~}{xwurqsspnoooonnmnoooppqruuwz}‡Ž’’’Œ„})!wƒƒ€{Y(ƒ~€€~‚jDƒ~€ƒ‚ƒ‚‚€~}~‚Ueƒ†ŒŒ“–—ž¢¨­±³µ´µQ2369H½ÍÉÊÉÇÇÇÆÄÿº·²®ª¦¡œ—‘Š…„€€€}}|}}}|zzzz{z|}|ƒ‚ƒ…†ˆŠ‰ŒŠ‡…†Š‰‡ƒ‚‚€~€‚€bYdhecabgkxƒ„|upnnmmjd__cfhhjmr|‡‹‹ˆ‚€}ww‚5676665332/,1BGDA:90  - _}‚„……„‡ˆˆ‹ŒŒ‹‡†„‚€~}|{zwutttpopnoopqpprrsuvxwxy{{}‚‰’’‘‡v%&w€~~{y‚G=…~||z~_H…|}€}{||yxxz||}„Ks‹‹‘”˜›ž¥§«°´¸¹¸´J1344J¹ÆÇÈÉÈÈÈÆÃ¿»¶¯ª§¢œ˜”ˆ…ƒ~|zzzxwvxywvwvuuvtvyz{y{~~„…ˆ‰†ƒ‚ƒ‚~}}~~}`[`fccbcglw€ƒ‚|wsoonlke_^`ceggjkmsz‚…ƒzxz}‚‡578645542219EIGB>?:   - - Ly‚ƒƒ„„†…†‡‹‹‡……ƒ€~|{xvuusurqqqpqrqrtuy{~ƒƒƒ‚‚ƒ€„‹ŽŽŽŽ‰‚}{m )z}{{|}}~~4Mƒzzx}S.RV[bdhopsvv{~|€~„E"ƒ›—–›ž¢¤¨«­±´¹¸¹¹»´I5693M¿ÆÈÊÉÈÇÆÃ¿º´¯§ š–Šˆ…€}|zxwvwvttutvtrrsrsqppqsuvxyyz|~€‚‚‚~€~~}zzyz}{`[`baabehox€~|xsnmlkhb^]beggeffimx€€}yw|ƒŠ7865533414BJIGE@<@  - :x‚~}€€€ƒ„„……ƒƒ€}{zwxwuvuurrrqrru{€…‰Ž”“’“Ž‘‹‰‹‹„}yui"({zzy{|||v&^ƒxv{M#(+06:R~„=`twz†‰–˜š›žŸ±»¼À·H7776XÆÊËÉÈÆÃÀ½¸²­¦ —’Œ…‚|{yvuttrrrqsttrqqpqooooooqrsuvuvxyx||||||~~|{zvvvvz|x`[___`bcis|€ztnmjigc^\`dfeggbglp{}}{vyˆ•77534322>JPJIFD>2   - - .wƒ~zyz|}}€ƒ…„ƒ„†„ƒ}}}||yxwwvrrtvz†‘—𠢤¤¥¥¢žš—”Œˆ†ˆŠ…|wro_PJE>Gwxzy{yzxx|dl{rwF/€€5!$&)+.1147›ÂÀõF7887hÌÈÈÇÅý¹´±¬¥ž—”Žˆ€|ywwwtrsrsrqprrqponllmmopnopprsrrstuuwyxxxyyzxwvtuvuwzv]Z[]_`delu}‚‚|tojkhie]Z\bdgffhfms{~|{{vv|‡™Ÿ7553229JVTPJHE:/!  - *f€|xxwz||~}~‚‚„†„…††…ƒ‚ƒ€{zyzyvy|~†Ž– §ª¬¯³±²µ´±¬©¢™’‰„ƒ…†…€ytoqsx{{wvwx{{|}|{z€V#&pvyB5€0"""%'(*+,//15ŸÆÁÇ®B:;<7rÍÈÆÃ¿½·²¬¦Ÿ™”Œ†€}zwvutssrrqrrqqpomlkjiklmmnmmooppppprrsttvusstuvuuututuwoZYZ^aceinv}€|vqniigfa[[aeejhhimu|€|}|zvz†”œ¢¨52123ASWTSKE<70,! - - - "[y~zwtrswwyz|€€„††‡†ˆˆ……ƒ€{{{yz‚‰‘™¤­±³¸¹»»»º¹¶´±¨œ„€‚„ƒ‚|wtrqprtsstuyz|}~}}|sjbXOI;[ww@A‚~ƒƒ. ""#&')*-/2359©ÇÂÇŸ76530…ÌÅľ»¶±¬¥ž™Ž†‚|yvutttrrrqqqppoomkjihhhghijijlmmmnopoqrqppqssrsrttstrsssukVW]`efimsy€yrnlgghe\Y\aefjkjnt|„…~{zvx‚‘œ ¤§31/-3AA?A=75320/#  - - - - U{{wrrqrrstyz}€ƒ„…ˆ‰ˆŠŠ‰‡‰‡…‚~~‚…‹“𥭳º»¼¼½¾½º¸µ±ª£˜Š~{|~€€}zyyvuttuuuvz|||~}|}}}|x{}|{xxoaZSKD<62,)$#I‚€†) "!#%')*+-33/<­ÃÂÆ›S\bv‹µÇþº³®§¤ž•†~zvuttsrqppqppoommljhhghhfdghfggijllmmnnmnpqppqqrrpqrpqqrpqtqbadfhjntyƒ}wojigge^XY]cfjjlpsyƒ‰†}yut{‡“œ £¤1.,./.,.43334422' - - - - - Lyxwtqpqrssty|„‡‡‡ŠŠ‰††††‡‰†„‚ˆ‹’›£«²·¹»»»¼½¼¹¶²«¢—Œ~{zz}ƒ‚‚~{zzzzy{zz|{{€‚|zzz{}|z€{zyzywxxwspnlhcYTQl…‰ƒ?2.2300.1656;@FGNSZv¾ÂÅÆÃÂÊÏÒÐÉþ¹´®ª —ކ‚|xvsrrqopooopoollklljjijmlifiigeffiklllmmnlnpomnopomoqponnnprqighjiovƒƒxqlihge`YX]cfijjmsw~…†€zvqs‹”›¡££.,+-.-/043566753   - - - - - - - - Euttsrsssstux{‚†‡‡ˆˆ†‡ˆˆ…†ˆ‡………‹Ž— ©±¶¹»¼¼¾¾¼¹´°­¨š‚zxyz}…………‚€~€~ƒ‚„„„‡…‡†‚‚‚€ƒƒ„„€|{xyz||~}~€‚‚ƒˆŽŽ‹ŠŒŽ‘““•™ž¡¤§¦©¬±¹¼¾ÁÆÍÊÈÉÉËËÈÉÇž·²ª£›‘‰„~yvtsrponmmmnonoomklnnnoorvurpmjihhfegjjilloonoononnmmlopnmlmmqpkijkoxˆ†{tnieeb`XV[_dghkmqw€‡ˆ€zwurx‚Œ”™ £¦,+,./00224677742  - - - - - - - :wwvvuvttuvwxy~ƒ„ˆŠ†…‡‡ˆ‡‡‰Šˆ‰Š‹•œ¢¬²µ·º¹º»º¹³­¤˜‘yutux{}ƒƒ†‡……‡…ƒ„„„„„ˆ‹ŠŠ‹Š‹Œ‰‹‹‰‰‰‹‹ˆƒ~ƒ‡†‡‰ŠŒŒŠ‘“‘“—˜œŸ£¤§¬²¹º¹½ÁÁÃÆÅÇÊËÉÊÊÊÉÈÉÇž·°«¥šˆ‚~{xtqrrponnlmnnnmoomnopprsux{yxvrqonkigfgigiklnnonnolllkkklllkmnpoljkqz‚„‡…zslhfccaZTW^dfhikqv{„‰…zvssu}‰‘˜œ ¤¥+++-/00344455762   - - - - - - - 1s{}}}|zxvvwwvzƒ†‰‹ŠŠˆˆˆ‡ˆˆ‰‹‹‘•™ ¨¬±³¶¸¸¸¸µ°¨ž˜ˆ~usqqqty|€‚†ˆ‰‹ŒŒ‹ŒŒŒŽ’““”“‘‘’“•“’“’Љˆ‡‹ŽŽ’”•““•˜——™˜˜¡¤¦¨©­°°·»¼¾ÁÅÅÇÇÈÊËËÊÊÉÈÇÈÇžº´ª¢›’‰~zwutrqpqpnnnnonooopppqqsstwxz|{{zywvutppjhhhjklmmmlmnljkkjjjjkijnrplov{„‚|rjgedb_]USX^cegimsx€‡Šƒxurrx‚Œ“šž ¡¢)**,-./11214112*  - - - - - - - - +n€„‡‡…„€|zzzzy~…‰‹ŠŠŒŒ‰Š‹‹ŠŒŽ’—𣫭®°²µ··´«¡œ–‹ƒ}vurqqsuw|€ƒ‡‘””••••–˜š›œœž¡Ÿ››žš™™˜•’Ž‘”““•—–™šœŸŸœœŸ§«­¯±´¹¼½¿¿¿ÁÃÆÇÇÈÊËÊÊÊÊÈÈÈȼ¸´¬¡™‹‚~yywuspnonnnmnnnoprrrrsttuuxyz{}}}}}}{|{zxunkjkkmnkllmmlljlkjihhhikmqnov…‡…~yrleddb`\VTV\bdfilot|„Š…}wsot|‡•—œ¡&''(())(''''%$#   - - - - - - - - "k‹‘Œ†ƒ|~}}„‡Š‰‡ŒŽŒŽ‘•™¤ª¬­®¯±´²­£šˆ€|yustsqsx}…‹’—™¡£¤¤¢¡žžŸ¡¤¥¦¨©ª©¨§§§¦¥£¡¡Ÿš–•—ššœ››œœŸ ¡¢¥¨¦¥¥§­°³µ¸¹¼¿ÀÃÄÄÅÆÇÈÉÊÊÊËÊÉÈÈÇÇÇý¸°¦•Œƒ}zxvvspnmmmmlmnpqqstrrstvwwyz{||}}€€}||}}|{xqmlkmmklkllmlkjjjiighfghiojju€ƒ{qjd^``^_ZRVZadefkkox~„…}xvqqy€‹‘“–™œŸ!! # ! !!"!   - - - - - - - - ]˜›š˜•‘Œ‡„„‚~}‚…ˆ‰ˆ‹Ž‘’‘’”–™ ¦ªª¬¬­°±¬¢™…|{xusutsyˆ‘œ¦¬±¯°®°´µ·³¯«©ª¬¬­®±²¯¯®­¬¬ª¨¦¡œŸ¤¦¦¤¡ŸŸ¢¦¦§§©¬®°²³±´·¸»¾¾ÁÄÅÆÇÉÊËÌÌÍÌÌÌËÉÉÈÉÇÆÀ¹´®¦š’ˆ‚{yxvtrpnllkjlnppqrsuvuwvwxz{{{|}~}~~}~}}}||ytqnnljkljljkjjjjhigfggfgjjYU_ijhfg`a^\Z]]ZTRX\`ccdgmpw}€|xtrorz…Ž“•˜™žŸ"""" !!##"#"!    - - - - - - - - Nˆ—Ÿ¡Ÿžš—‘Љ†ƒ†ˆŠ‹ŒŽ‘’’“”–™œ¢§ªª©ª«­­§›’‰‚€~xwvyz~‡˜¥«£‡mWMJJP_œµÂ½³³³³³´µ³´µ´´±¯­«¥¤¦«®¯«¨¦¦§«­®°°²²µ¹¼¾¾¼½½¾ÁÂÅÅÇÈÊËÌÍÍÎÎÌÌÌÊÊÉÈÇþµ¯¦ š‘‹|xvutrqonlloqqssrqstvwz{{{|}~~}~~€~~}~}|||xurppnlllkkjjjihihffdeeeicMJKSSTWYXY[[[[ZUQTX]bdeehlqx~}xurnpv€‰–˜˜š›œž !!"""!"   - - - - - <€‘Ÿ¦¥¥£ž™”І„ƒ…Š‹ŽŽŒ’““–™š¢¥§¦¦§¨ª©¥™‹‡†‡†ƒ…ˆŠ‹‘š¨±–c<0034453048Gw¯Ä»¹ºººº¸¸¸¹º·µ³±°²¸º»¸³±¯¯²´³´·¸¸¹¼¼¼½¾ÀÁÀÁÂÄÆÆÇÉËÌÌÍÍÌÍÍÌËÊÇÇÅÁ¼³­§ ™Š{xursqpponokWZ]aitvuxxxz|~~}~~~€€‚€~~}|||zyxusronnlklljifihfbcbbcf[FIJJMPQRVY[ZZZVQRV[^_cfeimqy}ytrpou~…Š“™™—š›ž !!" - - - - - - - - - - )q”Ÿ¥©©©¥¡›–’‹Š†ƒˆ‹‘Ž’••—››ž¡¡¢¡ £¦§¨¥¡œšœ›š›Ÿ£¤§«·§f0(/7;889:9<::5.@{¼Ã»¼¼¼¼»¼½¼º·¶¶¸¼ÃÆÊÆÅþ¸¹ºº»½¼¼¾¿ÀÀÀÂÄÄÄÄÆÅÆÉÌËÌÍÌÌËÉÊÉÉÈÆÄÀ¼¹°©¢”‡ƒzwutqrqpppoy>j{xxz|{|~~~~‚ƒ„…„…„ƒ‚~}}}||||zywvtrollkkjhghgeaababgM=BFIKNOQVYXYXWQOQVZ^`bdgkotyyuopqot…Š’–•˜šŸ  - - - - - - - - - - - - `Š—¢©«ªª©¤žš–‘Ž‹ˆŠ’“’“‘“–—™šœŸžŸŸžŸ¢§¨ª®²·¶²²²´·¹¼ÄšB'1524:95457;96784.Q©ÉÁÂÀÁÂÁ¾»ºº»¾ÄÇÈÑ—q‚Ž™¶½»»¼¾¾¿ÁÂÃÃÃÄÆÆÆËÐÐÏÎÉÊÉËËÊÈÉÊÇÅÄÁ¾¹´­£§c&*),+Mxstrqppont[f|ywxz{|}~€€„†‰‹‹‹‹‰ˆ…„‚}{{||}~~{zzxusrqnllkjhgfccaaac=3œÉÂÃÃÃÃÂÄÇËÌÌÌÆV16697P»¿¾¼¾ÀÂÃÂÃÄÇÇÇÇÏo0240KÈËÉÉÈÆÄÃÁ¾¹¶°¨ š“‰//qrqqqqpoyHi€|||}~€‚‡Œ”••”“‹ˆ†„~€€€}~}zxxwvurrrpopmlhdd>.379>DFJLNQSSPLOSUZ^^]_dghnsspnoprv|‚‡ˆ‹ŒŽ‘”˜   - - - - - - - - - - - - -  `‘Ÿ¦¬°¯¯®«¨¥¤¡›˜™˜˜›žžœ››™—•’‘‘‘“˜¢°ºÁÅÆÈËÍÍÏz-58::0N•ÁËÊÈÈÉÊÌǯf59;;:.PÀÆÃÂÁÂÅÉÊÌËÄÇ{+633576œÅ¾¿¿ÂÃÄÃÆÅÇÇÉÊË\49:4eÍÇÇÇÅ¿¼¹¹¶­§ ™‹t!-oqqrrsqsck€€‚„†‹‘”˜™˜—•“‘Œ‰……„‚ƒ‚€€€€~|{{zyxuxxvwuturp]/+18;;ADGJMPRQMKQTZ]]^]`dginropootv{|€‚ƒ…„†ŠŒ’–   - - - - - - - - - - - - - - - R€Žœ¤¬²µµ´±­««©£Ÿœœœœœž   žžœ›•’ŽŽ•¨´»ÀÃÇÊÉÑŠ-4696/sÁÑÉÅÅÅÅÅÆÄÁÃÃ…79<1MœÃÄÃÁÂÅÊÌÎËÂÆ¢//10114.kÅ»½ÀÂÄÄÅÇÇÇÈÉÍÁH6885~ÌÄÅÄÀ½º·²­§Ÿ™”‡ƒ`,npqqrsrw1n‚‚ƒ„†ˆ‰Œ“–™™™˜—•’ŽŒ‰‡ˆˆ†††††‚‚‚‚€|~|}|{{{|}|{zyws3%+-3:;?BEHMQRPJJPUY]^_`acdinmlmoswz„…„‡†‰Š‹Œ“   - - - - - - - - - =yˆ˜¡ª±´¶·¶³®­¬«¦¢¡¢  ¡¢¡ Ÿž™“ŽŒŒ‰‹Œ”žª³¸¼¾Àͱ707<71ƒÎÎÇÈÈÈÆÆÅÆÆÅ¾ȉ32a¸ËÃÄÅÆÉËÌÌÉÀ¼¹H&0/0001/?³½¾ÁÂÄÆÆÇÇÇÇÇË«:9874‘ËÁÁ¾º¸³¯ª£œ˜‘Š‚B#kpprspyRtˆ†ˆˆ‰ŒŽ‘•˜™œ›™—•‘ŽŒ‰Š‹ŠŠ‹Š‰‡…„‚~~~~€€}||U#*,7;?ADFKPOKIJOUY\]_``ceiljklov~ƒˆŒŽ‹ŒŽŒŽ‘’  - - - - - - - - - - - - 'j‚Ž™¡¨­¯²´³°¯®®­ª¥£¥¥¥¥¤¢¡ žœ—“‹ˆ‡…†‡ŠŒ’©¯²´¶Äs+:<=2ƒÒÇÅÆÅÅÈÈÇÇÇÆÅÅÃÃÉ„ÆÌÅÆÇÇËÍÌÊÆ¾´¼n!--../1241†ÆÀÁÂÄÅÆÅÆÇÇÇÏ”4:889ÆÀ¾½¹´®©¢”Šƒ|zy.eqrrrshv‰‹‘‘’”˜›ŸŸžœš–’ŽŽ‘ŽŒŒ‹ˆ…„‚€€‚„‚€~|r# - !(,7AGJJJIHJMPUXYZ[\_abeknt~‡Œ’”“”—™˜———˜™˜˜    - - - - - -  - - - - - - - - - - - 0u›¢¦ª­°°²±¯­­©§¥¦§§¦¥¥¥£¢¡›“І„ƒƒ„ƒ„†Š“Ÿ¨°¹¡-+052ÌÄÆÈÈÈÉÈÇÆÆÅÄÅÅÅÅÅÇÆÆÇÇÊËËÌËÅÁº¸³E(0./+::279:9¦ÅÀÁÁÁ¿ÁÄÆÅÂÆa3979E¯¼¸²­§£œ’‹„ƒ~zxu|ADtsrxZ)…‘‘•––––˜™š››¢£¢ žœš™™š™™š™—•’‘ŽŠˆ‡„…ƒƒ„„†ˆ‡Šˆ‰ƒ|j "(+/9?CHIHFGHLNRUXY[\]^_ajr~…‹Ž‘”•———•”—›œžœ  - - - - - - - - - - - - - - - - e€Œ™¢¨«¬¯°³²°®­¬ª¨§§¦¥¤¤¥¥¤£¡˜ŽŠˆ……‚‚‚ƒ…Š“ž¨´‘%(),2¦ÄÁÄÆÆÇÇÈÇÆÅÄÃÃÄÅÆÆÈÇÇÉÉËËËÉÆÂ½¸¾s$/0/01˜x-;8;6oÇÀÂÁÂÂÂÅÆÄÃÃS4765F°¶±ª¤¡™•‚€|yxutn"$%prwo"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4BBCDEFFGGIMRRSVUVWX\`_cfhlnoonnsyxy{€… - - - - - -  - - - - - - - - - - - - - - - - - - -  fŠ˜ §«­®°±²°²´³²¯°°°°¯°¯¯­¬©§¢œ–‘Œ†~~~~‰\­¨­°¶º¾ÁÂÂÃÄÅÆÆÆÅÃÄÅÄÄÃÅÃÂÈ£00113+I½À½½½Ãm08552lÇÀÃÄÃÂÀ¾Å‡+,(%!A—Іƒ~|wwxvuutsv1f|}/yš–M\ž›žŸ ¢¤¦¦¨§¥¤£¢¢£¤£¢££¡¡   žœœœž¢¥¨®±³·¸¹ºº¹¶²¬_ - -  (07>AAEGDGHFGJMPPQSTUVXY[_acedgihkmquw~„’˜ - - - - -   - - - - - - - - - - - - - - - - - - - Nz„“ž¥¨¬¬¬¯±²²³µ´±²²±±°²²²¯®«¨¤ž˜“‡ƒ€€~~~!F¥¡§­²¶¹½ÁÃÃÄÅÄÄÃÅÂÂÄÃÃÄÅÄÈ¿N,300/7¤Å»»¼¹¾œ13223C¶ÄÃÅÃÁ¿¾Ãx%&" Eƒ|yxvuuvutttr)p‡hO£š™Hd¦ž¡¢£¤¥¦¦¦§¦¤¤¢¡¢¢¡¡¢¢  ŸžžžŸ¡¦ª¬°´¸º½½½¼»ºµ´‰#  - - &/8?BBFJFEJHGILOOOPQTVXXYZ\^^acdhjnr}ˆ“œ¢¥  -   - - - - - - -  - - - - - - It€Ž›¡¦©ª©¬¯±³µ¶´²±±°°±±°±²²¯­§¡œ•Іƒ~}~†Z]¥¡§¬¬¯´»¾¿À¿¾ÂŪÂÅÂÂÂÄÄÃÊl-630.,€Î¾ºº¹¸¹½M-3251ŽÊÂÅÃÀ¾¹½l!% NŽ~|xutuuuuttui"%z‡‰A&‘£ £F ! k§¢£¤¤¥¦¨¨§©¨¦¥¤¢£¢¡¡  ŸŸŸŸž ¥ª®²µº½¿ÁÂÁÀ¿¼¸µ§J  - - - - $-8?EGIKJFGIIIHLMLLPRSVXWWY]^`bfltŠ™¢£   - -    - - - - - - - - - - - - Tk|‰”£§ªª¬¯±´·¹¸¶µ³²²²°¯°°°¯®¬§¢Ÿ—‹†ƒ~}}€€„6b¤¨«¯´¸¸»»ÂÂ~?vËÁÁÂÃÀÌ19632/.[…Ÿ²¼¿ÁÂÌ€,5460^È¿»¶ºa !V…}{xvuuuuvvuwc*„’œ}"d®££¨M!!"lª£¤¤¥¦¨ªªª«¬¨¦¦¤¤¢¡¢¡Ÿ  Ÿ ¡£¨­²µ¹¾ÀÂÂÃÂÂÁ¿»³®k - - - - - -,7HGFHJKIGIIIGIIIJLOOTUUVY^`cnt„—›œš™•“’“  -  -    - - - - - - - - - - - - - - agm€Œ—¢¨ª«®±´µµ¹¹¶¶´³³³²³³±±±°®¬§£ž•ŽŠ…‚}~€y$G•¦§­®®®³ºÁ®_/83ɾÁÁű?56651--&$,>Qit{…m364459¬ÄÀ¾º´®²S_}{yyzyvwvvxzy|[/›žŸ§V  1¢®ª©«U!$o®¤¥¥¦¦¨ª«ª¬«ª¨¦¦¤¡¡¢¢ ŸŸ ¡¤ª±µ¹½ÀÂÃÅÄÃÃÂÁ¾º°‹* - - - -/HKFHJJKKKIKIGHJKNPSSVZ_cglsŠ‘‰‰Š‰‹‹  - -    - - - - - - - - - - - - $hfg{‹”Ÿ§«¬®±µ¶···¶³²³²²²²³´´´³±°¬¨¤–‹†ƒƒk.g‘§­®°²l9'5997’ÈÁÄÄb287670+*+)*-*+-.0076446,tº¸¶°§§Bk{wwyyyyzzz{}†W0˜¤¥¥«ž2#$s¶­®«¯] #$k®¨©¨§§¨ª¬¬ª©©¨¦¥¢Ÿ ¡¡  ¡¢¦«±¶»¿ÀÂÄÅÆÄÃÂÁÁ»²£F - - - - - - 3HFIJJKNQQNPRPRTWX^`adjnruz|~|~‚ƒ…††     - - - - - - - - - - - - - - 'kda{•ž¦ª®°²³µ·¸¸·¶µ³³´´³³µ´³³³¯¬ª¦¢ž˜‘‹ˆ…€€}{ƒc'E\a[C+ )12482O»½Ä‡3989:3.%#&'+.232367754351D·ºµ²§ š7$xyxxzzz|~~€‚†Š“` 1¨«­«¸v'$=¬²±°¬¯`"%$b±ªªªª©©««¬«ª©§¥£¡Ÿ  ¢¢¤¦©­±µº¿ÂÂÃÄÅÅÃÃÁÀ½¶¯m  - - - -6@DCCGMQWWWYYXX\_adceikmoqrtttvx|}€…Š - -    - - - - - - - - - - - - - - - ,nf_o‡’›¦®±³´³³¶¸¹¸·¶µµµµµ´µ³³³²¯­«©¦¤Ÿš“Ž‹†‚€{z€`!&(,03.<¿À­=79:=6Kš‹`='$(./1466242256*ˆ½±ª¡™’,3yvxy{~ƒ†‡ŠŽ™k"#%3«­°²´³J%$u¹²²±®³l$(%\±¬««ªª©ªª©«ª§¥¤ Ÿ  ££¢¦«¯²·»¿ÂÃÅÆÅÄÃÃÂÁ¿¹´’( - - - -  -4;;:>EGMSTWTUTVXZ]`cegfijklqswwy|†Œ  - - -  - - - - -  - - - - - 4rj]cy‹—£ª¯±²³´´·¹¸¸···¶µ´³³´³³´³±¯¬©§¢ž˜•‘‡…‚}||‚s0 #%)+&J¤ÄºÁ_.;;<:7ż»®”vVGC?<>BD91541)I¯¦Ÿ˜‘"E}wz{|€„…ˆŠ‘–œo#%%(/–¯¯°²²¹•-<«²²³±¯µz(*)#P±®®­««©©©¨§¨¦¤¢žŸ¡¢¤¦§«®³·½ÀÂÅÆÇÅÄÄÄÄÃÀ¼·¦L  - - - - - - - - -#5435;=AEJNPQSQSVWZ^_cdddinsuw{†‡Š‘  - - - - - - - - - - - - - - - - - 9qj^`mƒ“ ¦«®¯±³µ¸¹º¹¸¸¸¸¸µ´³´³µ¶´´±¯­«§¢žš—”Žˆ…~}‚}L"#&f²¿·À-68<<5}ô³³´¹¾»º·´²³¸À–151-,$‡¤—Œy%5/-/...--..(o ¦{!''((±°°³´³»n{º³´³°¯²*(("A¯±±±®­«ªª¨¨©§¥  ¢¢¤¤§¬®²·¼ÀÄÅÆÇÆÅÃÄÄÄÂÀ»²u - - - - - - - - -%0147:;=BHLOSSQRUW[\_cfhlmtx|€„‡’–  - -  - - - - - - - - - - - - Csk^]f|‘𣍫®°³¶¸¸ºº···¹¹¸¶µµµµ¶·µ³²°®­©£Ÿ™”ŽŠ‡‚~ƒuF<е²±¸«>388;4ZÁ¹²²²²³²¶¾ÃÇÉÊÇÆÂN*-*(LŸŠˆp hª¨ˆ*++,*†µ²²´µ´µµµµ¶µ²±°³˜0))$:©´³²±¯¬¬ª©©©¦¢¡£¤¤¥¦©¯´·¼¿ÃÅÆÆÆÆÆÄÄÄÂÀ¼¹@  - - - - - - - - - - - - ,2269:<@DIMPSTRVW[^bfimqvyƒ…ŠŽ’•“•  - -   - - - - - - - - - -  - - - - - - - Gvm_]aoˆ•ž£§¬®±´··¸¸¶µµ¶·¶µ³´¶´µµ´´´³±¯­ª¦£ž›—“މ†…‚‚…‡‰xP/ ,Gz¡«ª«®´x4;864<¨À³±®¬­­°¶¿ÅÅÆÅÃÀÄ‚#)($ %‡……b #j¬¬‘1.-.+~º³µ¶¶¶µ¸º¸¸µ²±°´¢6)*)5Ÿ´²³²±¯­¬¬«¨¤£¤¤¦¦¦©®µº¾ÀÂÅÇÆÆÆÆÅÄÄÄÁ¾¶©w* - - - - - - - - +/48;>BBFLPRUWWY[`dhmty~‡ŒŒ‘†t  - - -   - - - - - - - - - Rwp`\af{™ ¤ª­®³¶··¶·¸µµ¶¶µ´´´´³²³³³²°®­¬©§¥¡ž›—”‘Ї‡ˆ‡ŒŠ‚ztt}£££§ª®²§¤¦ š–ž¾¸µ¬©©©«³¾ÅÈÇÆÄ¿¼¹§/$$ X…‡T $d­®™5,-,-s¸³µ¶¶··¸¹¹·´²±±³¨<(++.“µ²²°°°¯¯®¬§¥¤¤¥¦§©¬´º¾ÂÅÇÈÉÉÇÅÆÅÅÄþº¶‰c' - - - - - - - - - - - - 6:ƒ‚{l_cccedgz‰”Ÿ¨¯²´¶¸º»¼½½¼¼¼¼¼»¹¹·µ¶µ³±±±²²³²²²°¯®®¬­­­«©§¦£¡Ÿš—‘Їˆ†ˆˆ‰Œ•¢§¬³·»¾¿ÂÆÆÄ½»¸±°­ª­ª¨©ª­¯°²¶¸¸º»º¹º»¼¹¸··»½¼¼¼»»º·¶µµµ¶³´´µ´²±¯®®®­­­­°±°°²²´³³µ¸»ÁÃÆÉËÌÎÌËÌÊËËËËÊÈÆÃÀ»·³°­©£¢˜nadgioo` - - - - - - -     $&&$&(&)*(’’‘‘’ŒG  - - - - - - - - - - - - - - - - A‰†~o`bcgihgo‚™¤ª²´µ·¹¹»½¾½½½¾½¼¼»¹···µ³²²±°¯¯¯±°°°±±±²±°¬ª©¦£¢ŸŸ˜•’Ž‹‡†…‡‡ŠŽ“˜¦¯¶¹½ÀÄÄÆÈÈÅÄÁÀ¼º¹¹·¹ºººº»¼¾ÀÃÁÀÀ¾¿¿¾¼ºº»½¾½¼½¼¹¹¶µ´´µµ³´´´´²±°°°±°±±±²³³µ¶¸¸¸¹¼¾ÃÇÉËÌÌÌÌËÊËËËÊÊÈÇÄÁ»¸µ²®¬ª¨¡ž€cbehlrmb" - - - - - -  -  -  -  #&$$'&&())‘Ž‘ŽŽŽŒ‰J   - - - - - - - - - - - - - - - - KŒ†ƒ~saachkkkkv…’𤭱´µ·¸»¾¾½¼¾¾¾½¼¹····¶µ³²²±±¯­®°²²±²³²²³±¯¬ª©¨¦¥¡ž™”Œ‰ˆˆ‰‰Š“œ£«±¸»ÀÇÄÄÅÆÆÆÇÅÃÂÃÄÅÆÅÄÄÅÆÅÆÆÅÄÃÃÃÃÁ¿¾½¾¾¾¼¼¾¼»º¸··¶¶µµ¶´µ¶³°°±²²²µµ¶¶·¸¹º½ÀÂÃÅÇÊËËÌÌËÊÊÊÊËÊÉÉÆÅÿ¼¸´±­ª©¨¤™Šjcfikmund# - - - - - -  - - - - - - - -  "#%$%'(''()ŽŒŒ‹ŠŠ‹J   - - - - - - - - - - - - - T‹‡†‚va^chjlmklx‡•Ÿ«°°²µ·¹½¾¼½¾¾½½»¸¶¶¶¶¶´³³³³³²±²²²²±°±±²´³³²°¯®­¬ª¦¢™•Љ‹Œ‘–ž£­¤OHuÆÁÂÃÄÅÅÆÎÑÐÑÊÊÉÈÈËÒÑÏÈÅÉÎÌÍÎËÉÊËÆÀ¿¾¾¿¾¾½»¹¹¸·¶¶·¶·µ³±±³³´¶¹º¹º¼½¿ÃÅÈÊËÍÍÍÌÊËËÊÉÊÊÊÉÉÇÄ¿º¶³¯¬ª¨§¥¢œŽthfiikntmf$ - - - - - - - - - !!"$$$'&&()(ŽŽ‹Š‹Šˆ‡‡‰D  - - - - - - - - - - - - ^Љ„|b`cfklllgo“§®±´¶¸¹»¾¿¾¼½¼¼»¹¸·µµ¶µ¶´´µ´´µ³´µ³´´´µµ´µµµµ¶´±°¯¯¬ª¤ œ˜—–‘ŽŽ”šŸ¯e ,,žÅ¾ÂÄÄÄÄ~hl˜ÑÊÉÇɽr’ÊÉ­jhfdbgrŒ«ÇÉÂÁÁÀ¿¿½»¼»º¹¹¹¸·¶µ´´¶·¸¹¼¿ÁÂÄÅÇÉÊÌÍÍÌËËÊÊÊËÊÉÉÈÇÅ¿½¹·´¯­ª§¦§§¥Ÿ–‚qiikjkqtlh+ - - - - - - -  -    "#$%%$#'&&‹Ž‹ŒŠ‡†ˆ†…ˆE  - - - - - - - d•Ž‹‡jbeghllmjjx‹™¢©°µ¶·º»½¾¾»»½¾¼¹¹¸·¸¶µ¶·¸¸·µ···¹¹¹¹¹¸¹¸·¸¸¸···¶µ³³±¯ª§¢žš˜•‘‘‘’”œ–*%,)\Ä¿ÀÄÃÃÇ\)//}ÐÈÅɼE.MÃÌ«312:>;5.6^¡ÍÇÄÅÄÄÄÃÃÁÀ¾¼¼»ºº¹»»¼¾½ÀÄÅÇËÌËËÍÍÌÍËÊÉÉÈÈÉÉÉÊÈÆÂ¿»¸µ³±®«ª©¨¨§¥ ›xmjkkknuvml/ - - - - - - - - - - - -  - - -  !"#$$$%&&&(ˆ‰ŠŠ‰‡…‡‡„ƒC  - - - - - - - - - - - - k•Žˆodfgikklkjp} §¯´¶¸º¼½¾½»¼½½»»º¹¸¸·µµ¸¹¹¹¸¹º»¼½½¼½¼½¼»»»»º»¼»¹·¶µ´°®«¦¡ž™••–•‘œa%$**•Áº¾¾¿Çy076,nÉÈÃÇ]6?±È»H6G«º·³–b-3vÊÉÆÇÈÇÇÇÆÆÆÅÃÁÂÂÁÁÂÄÅÅÈÊÊËÍÍËÊÌËÊËÊÉÈÈÇÇÆÆÇÆÄÀ»·³°­ª©©§¨§¨¨¤¡œ“‚qlilklqwxnk1 - - - - - - - - - - - -  -  -"#$#$%&&((‰‰‰Šˆ†„…„ƒ„? - - - - - - - - o–‘މ‚qcceggijjiiuˆ˜£­²µ¸¼¾À¿À¿¿¿¾¼»»º¸¶´³µ¸ºº¹»½¿¿¾¿ÁÁÁÀ¿¾¿¿¿ÀÀ¿¿½»º¸¸¸¶³²¯ª¦£ž›š˜–‘/!&!L´²¶¸»Ä2622,kÌÄÉn36›ÆÅ[1A¹ËÈÉÍÎ’8+eÉÉÇÉÈÉÉÈÉÉÉÈÇÈÉÈÇÈÉÊÊÊÊÌÌÌÌÌÊÊÊÊÊÉÈÉÇÆÅÃÂÁ¿»·±®ª¥¥¥¦¢¢¤¥¨¨¥Ÿš‘{mllnlnt{|pp5 - - - - - - - - - - - - - - -  #$##$%$'('ˆˆ‡‡‡„ƒ„ƒ‚B  - - - - - u—“‘Š„vbccddfggigk~’Ÿ¨¯´º½¾¿¿ÀÁÂÁÀ¾¿½½º·´´¶¹»¼ºº¼¾¿¿ÀÁÁÀÀ¿¿¿¿ÁÂÁÀ¿¿¼½¼»¼¼º¹¶²¬¨£ ž˜œj.r!†¯¬²³¾-1RR10lÈˇ23ˆÇÈo25¢ËÅÅÄÅÐ6.ƒÏÆÇÉÉÉÉÉÊËÊÉÉÊÈÇÉÊËÊÉÊËËËÊËÉÉÊÊÉÉÇÆÂÀ½»»ºµ±­©¦¥¢Ÿ   ¢¤¦¨§¦Ÿ•ˆsonnnlou}|ot= - - - - - - - - - - - - -  -  !!"#&%%$&‡††…„‚ƒ€}}: - - - - - - - !™”‘‹†{ecc`adffeeiu‰™£«±¸»¼¿ÀÀÂÄÄÃÁ¿¾¼¸··¹¼¼¼½¼ºº½½¾¿¿ÀÁÁÁÁÁÀÁÂÁÀÀÁ¿¿¾½½½½»¸µ³®¨¤ ™—7XžEA§£§¬´)+^¬<4-xÏ–32qÉɆ03‡ÎÅÇÆÄÃÅY1E¼ÉÇÈÉÈÈÈÉÈÈÇÈÈÈÇÇÈÈÉÈÈÊÊÊÉÉÉÇÇÆÆÅ¿»¸·´²³­¦£ ŸŸŸœ››ž¡¤§§¦¥›“}npoonmpy€{qx? - - - - - - - - - - - - - - - -   "#$%%%&&……†ƒ~~|}4  - - - - - - - - %…™–‹…{hcc_`ccdeegk|ž¦¬²¶º¾¿ÁÃÃÃÃÁÀ½¹¶¶¸»¼½¿¾»»¼¼»¾¿ÀÂÂÂÂÁ¿ÀÁÂÂÂÁÁÂÁÀ¾½½¼»¶´³¯ª¥št!‚w}¢¤¬Š((RÑ/4,ª72_ÇÆœ56iÍÄÅÃÂÀÈs44™ÌÆÇÈÈÇÇÇÇÇÆÆÇÇÆÆÆÇÇÇÇÆÈÈÆÇÇÄÂÁÀ½¸µµ²²°­«¥Ÿœœ™š™™šœŸ£¦¦¥ ˜soqrrpnr…}szE - - - - - - - - - -   "$%$$$$$…„„‚ƒ€~}z|8   - - - &†–”†|hcba`abcdfgem|–¢§¯³·¹¼¾Á¿¾¾¾½»¹¶¶¸¹·ºº¹¹ºººº»½¾¾¾ÀÁÁÁÀÀÂÁÀ¿¿¿¿¿½»½»º¶³±­¬¥EO”ŠŽE>˜•š ƒ%#I±¶r'-1|;2N¼À¨?7PÀÁÀ¿ÁÃÇb21”ÉÄÅÆÆÅÅÅÆÆÆÅÆÆÅÄÅÅÅÄÄÄÃÃþ¾º·´²®®«©¦§¤œ™–•”’’‘”—šŸ£¦¥¢š“€lpssrppv‚ˆ|v|P - - - - - - - - - - - - - - - - -!#$$$%&%‚„ƒ€~~|}|{yz= - - - - - - - 'ŠŸ›–މ‚kcbb`_``cefedn†›¥¬²µ¹»½¾¼º¸¸¶¶··¶´³³´µ²´µ´µ¶·ºº¹ºº»¾¾½½¼½»¹¸·µµ²°®­¬«©§¦¤žž‡azvtYlЇv; Ÿ¦B %%*,<¯»±A2=´¾½½ÀÊŒ67>¬Á¾¾¾¾À¾¾ÀÀÀÀÁÀ¿¿¿À¿¾¾¾½½¼½¼¸µ²¯¬¨¥¡ Ÿœš•‹Šˆˆ‰Š‘•›Ÿ¢¤ œ“…roqsrqpr|†Š}z€Z - - - - - - - - - - - - - - - - -  !!"%%&'%‚„€~|{zz|{xz? - - - - - - -  +ŽŸš–’‹ofdb``abbdefghwŒŸ§­±¶»»»¼¹¶µ³³µ·¶³±­®­­­­­­¯±²³³¶¶¶·¸¸·³°°°¯«©¨¤¢¡žœ››˜–Žˆ‘D*|y~m-“’Ÿˆ# ##%1¥´±E-.¢¾¸»²0-&c³¯³±±³³³´³²±²³³²²²³³³²²°°¯­©¤¡Ÿžœœ˜—•’Š…ƒ~€ƒ†‰”™Ÿ¤¦¦£™Švmortrqqu€‹‹}~…d - - - - - - - -  - -  "%%%&'€}}|z{{zwv< - - - - - - - - 2‘𗓆thfdba`abdefhej“¢¨¯³¸¹¹¹¸¸¸¶¶¸¸¸´±®¨§¥¤£¥§©§©««««©«««§¢¢¡Ÿžœš™•’Š‹Šˆ……~pKyte*„ƒ„’c#Š¡ =^sfT80„–‘“—™››œœž £¢¡£¢£¥¤¤£¡ Ÿžš——”‹ˆ…„{{}€‚„‹•šŸ§««¨¢”‚qnprsrqsx„ŽŠ{€†g - - - - -  - -   #$$&&'}}~€|{z{xywuC - - - - - -  2’ž›˜’ˆ{jjhda__abfehhfr‰¦°´·¸¹¹º»½¼¼½½¾»¶²ª¤¡žœžœ›ž ¢¡ Ÿ  ž—ŒŠ‹†‡„ƒ{|{vxzussrxB$T[]\]^^L!Zja nsrsx?]rx1">gonsz{ƒ††‹‹Ž‘‘“”—™›žœœœš—”‘Ž‹ˆ…~}{|z{€ƒ‡”—ž¢¦­®¬¦šŠtmorssrru|‡”‹}ƒ…d - - - - -  - - !###'('}}~}|zxwuusuE - - - - 2”Ÿš“‰{mllgcb`abachiho|‘¡¬²¶¸»¼½¿¿¾ÀÁÀÀ¿º´°©¢›š˜™š››››˜–“‹{yyywvtttrrqoolkje<59[kiiffffj]EC@\e@9:_iihhkI+*)Vfg?,1449AIS_hegjmqru{|y{z|€€„‰“—šœš˜–‘ŽŠ†„~{yz|}~„Š˜›Ÿ¤©¬¯®©¡“|mnpttssrv€Ž•‡€‡Œh - - - - - - -  - - - !##$')){|}|{yxturquP - - - - - - - 7— ž›–Šqqnleb_abcceikio‡œ§®´¶º½¿ÀÀÁÃľºµ±«¦¥¡ž›˜˜™š˜™˜”‘Œ„zvtutsrqrqnmmkhgea^]``^]^[^]^``aaa`^^_`cadeddcfcbb```_^`baaaababdfiknoqtutuuwzzz„Š‘•šœœœš—”‘ŽŠ‡„€~|zz|~‡Ž’™ž¡¦ª«°°­¦›smpqtuussy‡””„‚Šo - - - - - - - -  -  #$$&(+|zy{ywttssorR -  - - - - - - - - - - 9›¡ ˜’‹ƒvtumhea`bdcehjhiyœ§¯³·½¿ÀÀÃÄÃÂÿ½¹µ¯­¬¨¡ž›š›œ›™—“Œ„}wrrsqoponjijffggecdddefecddefgeddcbdcddcdedccabb````^^]^`^^_aaddeilmoopqqruwy{}€ƒ…ˆ–ššœš—–‘ŽŠ‰ˆ†‚~{|}}„ˆ‘”™ž£¦«­¯²¯­£”~lnpsstuuv–”„…Ž’u  - -   -!#%%'+{{z}vvtssrpmI  - - - - - - >¢¡˜“Œ†zuwrnhdabdcehiihmƒ”œ¦®µ»¾ÁÂÄÅÄÂÂÀÀ¿¾¹³¯¬§£¡Ÿž›™˜–’І€{wvrppommlnmmmoononnmmopmlmnmjjjhfghhhhghifefeddedbab```a`bccdfdejnpomoprsw{}~€ƒ‡Š”›žœ˜•“‹ˆ†…ƒ~}|~‚‰‘—œ ¥ª«®¯³²°¬žŒomprtutuvyƒ’™•‡’–x - - - - - - - - - -   -0!$%&&+yyzyuvssqpnnP  - - - - - BŸ¡¢ž™”‰}u{yrmgcbcdegjjlku…–¢©±·»ÀÂÄÂÂÁÀÀÁÂÁ»·´°¬¨¥££¡šš–’Œˆƒ|zwttrtvwxwwvvwvvuvtrqsrqpqqpponmmmnlmkjjikihghhgfddcccdcdeefghjmoponqsw{~‚ƒ†‹ŽŽ•š¡¡žœ˜”‰‡…‚€|}}„„Š”™ž£§ª¯±°±´²®¥–zkoqstuuuv~Š—œ”‰•˜y - - - - - - - -  * "#&&)+wxwvttrrollkW - - - -  J¡¢£ž›•މ€uz{uqleabcdfijkkm{™£¬³¸¼¿¿ÀÀÁÀÀÃý¹·³°«©¥¢ Ÿž›—”“ŒˆŠ‡‡„€€€„‡ˆ……‚‚‚€~|{yzxwxwuuttttrrrssrqnmmonmlllkihhhhiiikikmnpqsusrvz‚ˆ‰‘““•˜š›Ÿ¢¢ ™–‘ŽŠˆ†ƒ~|€‚†‰Ž–œŸ£¨«®±´´´´²«‰qmosutuvvz‚‘›Š“—šz - - - - - & "#&()+-ywttrrronlkhb - - -  N¢¢£Ÿ›•‹„vw|yuqkcbccegikkkr‘§®´¹»¾ÀÀÁÂÂÃÄÄ¿½º¶²®¬§¤¡ œ˜–”“‘“Ž“šŸœœ™˜™—•‘‹‹Š…„‚~}}|zzz{|zwvuvuvvuqsqqppnpqosttwy{ƒƒ„†ˆ–———˜›œžžŸ¢£¢Ÿœ˜“Žˆ…„ƒ€}{~‚†Š•›¡¦©«¯²µ¶··³­£‘tnoptvvwxw|ˆ˜œ‹Œ–˜{ - - - - - - - - - -  !' #&*)*+/ttrsrpmllkjfd - - -   P¦£¥¡›•ˆxx~|ysngabbdghjlmox‹— ª¯µ¹»½ÀÂÃÃÄÅÅÁÀ¼¹¶±®ª¦ œ™–””“‘”••˜™£ª±´´²±­®ªª§¥¥¦¤¦¥¤¡ž›œ™˜•””’‘“‘Žˆ†…ƒ‚€€€~~€‚ƒ‚‚„††‡ŠŽ‘™Ÿ¡¡ ¢£¤¤¥¦£¢£¥¤ œ—“Žˆ„~~|}ƒ‡Œ‘—œ ¦©¬¯³¶¹¸¹¸±§œ„lnqswvwyzzƒ›Ÿš‡™›› - - - - - - -  #'#&*,-.13trsqonljjihec - - - -   N§¦¨¥ž˜“‰}v|~zwqkdaccdgjkkmr}™ ª²·º½ÁÄÃÄÆÇÅÃÃÀ¾»·²®¨¢™–“’“‘’“’—ž¤©±·»»»»¸³±±´µµ¶··¸·´²±°°®¬¬ª§¥¦§¦¥¢ž›š™—•”””•–“’’–—™šš™™š›œ›› ¢¢¥©««ªª«­­¬­«©¨¦¦¡™‘‰„||}~ƒ†‹’˜£§ª­±µ¸¹¹··³«ŸŠtmoquwuvyz‰•ž¡–„‘™›  - - - -$&!'*-.035sqrpmlkhhgddd - - - - - -  M¨¦¨¥ œ–‘‹€x|~~{umhbcbefijjlpvƒ˜¢¬´º½ÀÃÄÅÇÈÅÄÄÄ¿º¸µ¯¨¢˜”“‘“•žª±·¼¿À¼»¸¶º¼¾ÀÁ¿ÁÃÄÃÃÃÃÀ¾¾¼½¼º¶¶¸·¶¶²±®­«ª©§¦§©¨¦¦§¨©­¯±³¯°°¯¬¬­¯±²³³²±²²´´³²¯¬¨¨¥ –Š„€~~|}~ƒ†‹“™¤©¬®²¶¹ººº¶²­£–ynopsvwwwy|‚œ¢¢‘†•œœž - - - - - - - - -  &&"$+/0246pnqmmkffffcac( - - - - - - - -   M§¦§¤ ž™•…y|~€{tneabdfgjjkou{‡”ž¦®¶»½ÂÆÆÇÇÄÄÇÆÅÃÀ½»µ°¬¦¢›™•’“™¡¨±º¿À½»»¾±±ª§§¥¥ž—˜—–”—¥·ÂÄÀ½¾¿¿¿¿¼»¼¼»º¸··¸º¸·¶¹¹½¸ª˜‡„Œ•£´¿½»»»»½½ºº»»»¹¸·³°®¬¥œ‘Š…€~}„…‰”˜Ÿ¤¨­´¶·»¼¼»¹´°§œ†nmpsvywvy|‰– §£‹†–Ÿ ¡~ - - - - - - - - -  -)(#!#(/4567mmlhkjgeddcb`3  - - - - - -    S¤¨©¦¡™–’Šzy€ƒ€}xslb`bdfgjlnqv~Š“¥®¶»¿ÅÆÇÆÅÆÈÈÈÇÅÁÂÀ¼¸³¯«¨¦£¥ª²¹¼ÁÀ¿½»»¾¼RA=;;<;98<7476=K\¯ÆÄÀÀÀÀ¾¼½¾¾¾¾¾½½½¼»ÀÁ¨~X?320,.39KpžÂÇÀÀÂÃÄÃÂÁ¿½¼º¸¶´¯¥™„~}€†‹”™ ¦ª®³¸º¼¾¾½»¹²¬ tmmpuxywv{„›¤§¡ˆ‹™¡¡£z - - - - - -  -((&!#*46889jliihhfccc`\[9 - - -   W¨ªªª¥Ÿ›™•‹~w€ƒ€{tpgba`cgijnoru}ˆ“𤝵¼ÁÃÆÆÅÆÈÉÉÈÇÇÈÇÄ¿»¹¶µ´µ¶ºÀ¿¾¼º¹¹¹»±?9:99::8;:6779;;5.@z»Æ¿½¾½¼½¾½¾¿ÀÀ¾½¿Á¢f:+01/430111,*7VŒ½ÆÀÁÁÀÀ¾½¼»¸·µ²ªœˆ€ƒŠ‘—›£§­±¶º½¿ÀÀÀ¾º¶­¡–}lnptwzxxyƒ‰–¡©©›ƒœ¡¡¢w - - - - - - -  -%)'"!!%17::aa`_]^]WVVSRPJ - - - -  cª©¬¬ª§¢Ÿš“ˆ}}„………zunfa^^aejlloru{„–Ÿª·¼ÀÁÂÆÉÊËËÌÎÍÌËÊÊÊÉÉÇÅÃÀ¿¾½½¼¹¶¶µµ•+)))$PŒŒ‘‘„pJ.(10/311’ø¹º»»¼»¹ÂŸ?0111/00),044.++2530...(k¸±®ª§£ š•‹ˆƒ€€‚…‰”šž£©¯´¹½¾¿¿¿¾½º¶±ª‹{opqtw{{{z}„‰œ¦®­¨‰‡š¤¦¥¥j  - - - 3M3$$"%%%,38:___\[XXSSUQPLE - - -  c¬©­®¬¨£Ÿ›–Œ~ƒ‡‡†‚}xslea_``ehjloqv{…‹•Ÿ®·½ÀÄÈÊÊËËÍÎÍÌÌÌËÌËÈÇÈÆÃÀ¿¾¾¹·µ±¯®'&$%q³®±²µ²²´²“K&-,..1;§»µ¹¹¹»ºÂ¥=23/11,);]™ ¡šŒmG--0,,.,"q¯£¢ œ˜”ˆ„‚€‚…ˆŽ”›Ÿ¤ª°µº¿¿ÀÁÁÀ¾¼¹³«¢‘soqswz|{{|€‡Ž• ª¯®¤‰ž¦¨¦¨f - - - - - - - - - - - - -#(1G2$%!$%'-138]\[XWVTRQPMLHB - - - - -  e­«®°®ª¥ ˜‚~ƒŠŒ‡†‚|uphebb`bfhjlnsu{„Š‘ž«¹½ÃÈÊÊËËËÊÌÌÌÌËÊÊÈÈÊÈÆÃ¿¼¹µ³²°®¯Ž#$#$ p²­¯°±°®­²ºµc'-,--'Y¸´µ¶¶¸º¹L010.0++k§½¿ºº½¿¿Ã´:&,)&##$…¢™™•‘Їƒ€‚„†‰Ž•›¡¥©®´¸¾¿ÀÂÃÃÁ¾»·­¦›‡vopquy||||„Š‘™¥­±®˜Ž¢©¨¥¨^ - - - - - - - - - - - - &)*-@/!#"$%+115ZXVRRSROMLHDEB! - - -  j««°°¯¬§¢žœ“‡€‰Œ‹ˆ†€xsmhfdcbahikmqruy€†Ž™¬·¼ÁÄÆÇÉÊÊÊÊÉÊÊÉÊÊÈÊÉǾ»¹¶µ´±®²#$#%#t´­°³µ³°°´´·»]'.+,+-“º³³²³¼q,/-,.(5‹»¶´´¶¸ºººº»Ä«P"&!"?˜““ŒŠˆ†…ƒ„…†‰Ž•¡¦«¯³¸¼ÀÁÁÃÄ¿»·²ª|ooruxz|{|~ƒ‡Œ•Ÿ©°±«“£©¨§§W - - - - - - - - - - &())/A,"$!%%)034VSQONOLJIIFEC? - - - - -  fª¬±²°®¨£ ž–ˆ€ˆŒŒ‰‚~wpmifcbaaeimoostzƒˆš©´¼ÀÃÅÇÈÊÊÉÊÉÈÈÉÉÈÈÈÅÂÀ½º·¶¶²±²Œ$&#$#p³­±³´³´´µ¶´·­>'++,&]º±²±´Ÿ4**)+(5𷬮°±²´´³´´´²¶±Lz•Œ‹‰†…„…‡†ˆŠ”œ¡¥«¯³¹½ÁÂÃÄÄľº±ªžrmoswy}}||€…‹’›¤®±±¨†–¤¨¨§¥S  - - - -  - - - ((()(+7*$ $%'/43SSOMLKLIFEDC?=& -  k«¬²³±®«§£Ÿ™Š€†ŒŽŽˆ„|vrkeddcaafklnqru}ƒŠ¬¶º½ÀÃÆÈÉÉÊÊÉÈÈÈÈÈÇÆÆÃÀ½¸¶µ´±³Ž$%$%$q²®²³´³´´³µµ³¹„'-+,*7§´²±·j#)(')&†¶«®¯¯°±±°°°°±®©ª›1P‘‰‡‡…„ƒ†‰Š”𡦩­²·»¿ÂÅÅÅÅÃÀ»µ«ž“ƒspoqvy{~~|~‚‰— ©¯±°¡}„›¥ªª©§N  - - - - -  ))()))+1*"!"'/35QOMJJFGECAAA><. - -  p«­²²°¯­ª¦ œ€€ˆ‘‘Ž‹ˆ‚|wqiggfbbbeimpqsuzˆŽ—£­²¹¾ÂÄÆÈÈÉÉÈÇÇÈÈÇÈÉÉļ¹¶µ±³Œ$%$%#t³®°²³´´µ´³³²³®;++*+*‰¶®±­@"&&&!X°««¬¬­°±°¯¯®¬«§¤Ÿ£g4‡…„†…„…ˆŠ‹‘–š ¦ª¯¶¹¼¿ÂÅÇÅÆÂ¿»µ¬Ÿ•voprux{}~~|…Œ”œ¤­±´¯—|Œž§ª«§£E  - - - - - - *)(()((*3*#!#-44MIGGHED@?@?=>:/ - - - -  r¨¬²³±°­«§£Ÿ˜‡‡Œ‘’Š…€{vojggeda`fhlnorsy~~‡‘¡­³¸¼ÀÃÅÅÇÈÈÇÈÉÈÇÇÈÉÇÅ¿½ºµµŒ$&&(%w¶±²µ¶¶·¹¸¶µµ´»Z$))+&m¹¯´—+&$$$+”°ª¬®¯±²±°¯®¬ª¨¤£Ÿš‰''€………„†ˆŠŽ‘”£¦¬±µ»ÀÃÅÆÇÇÅÄ¿»¶­¥•„uonqtwy}~~}~ƒŠ– ©¯³²«Ž‘¢«®¬¦=  - - - - - #)()(()))*/-" ".33GFFDBAA?>>=<;73 - - - - -    sª«²´²¯««©¥ šŒ‚‚‹’“Œˆ†ztnihhfcbbegjmqsuv|€…‘¢«³¸½ÂÄÆÆÆÈÈÈÉÉÈÈÈÆÈÇÆÄ¿¹ºŽ%()*'~¹²²¶··¸»»¶¶µ³¹n())+'^¹²·~$'#%$Gªª«®®¯±°®­¬¬ª¨¥¢ ™‘’;'|‡ˆ‡ˆ‰‹Ž‘•œ¡§¬´¹½ÁÆÈÊÊÉÇÇÀ¼º²§™†wnmptx{}~}}‡Œ“›¤­²µ²¦…–¨¯¯¬§›; - - - - - - - #')(((*((''2- '/2GCAA?;<<=;<;876 - - - -     s­¬²´´±­«ª¦£œ…‚Š“”’‘Œ‡„yrmjihgebcejmprruz|€„‘ «²·½ÀÂÅÆÅÅÆÆÇÇÈÉÉÉÇÅÄ¿¼¿(-+,+‚¸³µ···¸¸·¶´³²·z&)')'U±«±j $#%!_«¥©©©ªªªªª§¦¤¢¡™•‘“G%}Š‹‰ŒŽ”˜œ£§¬³º½ÂÇÈÉÉÈÆÃ¿»·°§›‹|tkortx{~~~~„‹— ©±¶¶±Ÿ~‡›©®®¬¨›4  - - - - - - $(('(')('''(0-$,/DB>=<988987875/ - - - - - - -   oª«²µ´³°­©§¦ –‡„Š’••”’‰†‚~wrnkjihfdegimppruxz{Š›¨­´¹½ÀÂÄÄÅÅÆÆÈÉÉÈÆÄÄÃÁ¾Å‰-/+-.Џµ¶··¸·µµ¶¶´²¹%)&(%M®¨°W!"% o©¤¨¨©©ª©ª©¦¤¢¡Ÿš–”Ž“I+…Ž‘•—› ¥«¯·½¿ÂÆÉÈÈÇÄÄÁ½¸±¨Œ}ummptw{~€~~‡Ž”›£®³·´¯˜|Œ©­®­©˜, - - -  - - - - - &('''%&&&'''(/+(,A?<<;999844543- - - - - - {©¬±´´³±®«¨©¦™‰„Š’˜˜˜—”‰†‚{upnllkjhgfgkmpqqtvxz„•¡«±·¼ÂÄÄÄÅÅÅÆÇÇÆÅÄÄÄÃÂÈ|-210/–¾¸¹¸¸¹¹¹¸¶¶µ²ºs%&$&#P¬¤ªP #"«§©«©««ª©¨¥£¢¡ž›–“’E3’“••˜œŸ£¦¬±¶¾ÁÃÅÇÈÈÇÅÂÁ¿»´«¢’ulmrtv{}~€€€„Š’™ §°¶¸³©‡{’ ª¯¯¯©' - - - - - - - - -(''&&'&&'&$%&%/0%*<>>=9866543420/ - - -    0ާ®²³´³°¯­ª©§„ˆ•˜šš—’Œ‰„ztollllljgghknqqrtuwyzœ¦±¼¾ÁÁÂÃÃÃÄÄÅÆÄÆÆÆÄÃÉo59:7:¦Áººººº»º¹·¶´±·Z!$#% Z¬¤ªQ!xª¦§§¨©©©§¥¤¤¢Ÿ™–“‘“=?˜•—™ž¡¥¨­³¹¿ÄÅÇÉÉÉÈÆÅÁ½º´¬¦—‡yrmpuwx|~€€‡Ž—ž¤«²¶·³¢‚‚–¥­°²°©Ž"  - - - - - (''''&&&&&%%%$&+-$'::::754420//.-) - - - - - - - -   T›¡­³µµ´±°®«©¨¢‘……‹’•˜˜—•’Œ…‚~ytommmnmkhiilpqrsttvxy~‰—¢¯¶»¾¿ÁÁÃÃÃÃÃÅÇÆÆÃÁÅ_89>=H²¾»º¹¹¸¸·¸¹µ°¯®?$$"$i«£«Zd©¤¦¦¦¤¥¨§¦¥£Ÿœ›˜–”—Š(Wœ—œŸ£¦«¯²·¿ÆÈÉÊÊÊÈÈÅÃÀ¼³«£—†zrnpuxx|€€„‹•œ¢©¯¶¸¶²—|†š¨¯²²¯ª…  - - - - - - - - !('&'''&%%%%%%$$$-0$776753210.-,++* - -  {¡ ¬³µ´³²°¯®«©¢•Š…“•˜™š™•‘‹†ƒ}xsppnmnolkkknprsstuusuz‰™¦´º½¾¾ÀÁÂÀÀÅÅÄÃüO:9TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y- - - - - - - - - -  33-,,,++*+*+*)*)))++**)(()&((&' - - -  - - -  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*3./3A­º¸··¶³³²°®¬«©§¦¦¥¢ž›—“‘‹Œl H˜”w!!ƒ¡ ¢£¤¢¢  Ÿ  Ÿ¢¦®´¹½ÁÃÀ¼»½½¸¬šŒŠ•ž¨¯³²²µ·¹º·³ª–©°³µ¶·¶¯©²¯£F  - ##$##%,,,/0.164.-,+********++*)))**(''((+.0/4956:758;89=@9:;7 B±ÇÎÏÍÉÊÊÆÂ½»¹§—“˜£«±¶¸¸¹¸·¸¹»½¼ºµ®£›’‘˜š˜”‹†„‚€{x(/rrkW}{y%/ššœ ££¤¨œ3$&%'%c·±³µ¹¢1,.,14ƾÁÁÁÁÀ¿Å21,+0T·´µ´²±°°±¯®¬°®®®¬¨¤žš“‘‡†‹‘z$<”“%d¥¢¤¤¤£¡  ŸžŸ” ®°·»½Á¾¼¾½»³¤”ŽŒ‹Ž–£¬±±±²µ¹º»¹²¦‘’ ª²µ¶¶··µ«®¯¥‘…f - - - -$##$$&-/.11-/172-*-+*****,*)()*))***''(),1-.11011345578<<==<5 V½ÇÎÎËÈÊÊÆÁ¾¼¸©š“™¢©®²¶¸º»º¸¸¹¼½¼¶°©Ÿ•Ž”›š—•’‹†…ƒ~}/'suyM#r}|}**„”•“”˜™›¤b  1›¯¬­®®³S&*(+*_¿¹ºº¼¾¾ºÁ}**)(*]µ¯°®®­¬¬«ª¨©s`b_[USKC<81`‘’‚)0Œ•Œ,:¢§¥¤¢ œœœœ JV­¸¶¼¿ÂÀ¼½¾½º¬™Œ‹Œ“ž¨°±±±³¶¹ºº¶¯ •£­´µµ¶¸¹¸­­°©™…z, - - %$$%%)-0/00-,-26.+,,**+**)**(**)))))'(,+-../00//3355565799:95 - vÃÇÎÎËÉÌÉÄÀ¾½º¬”–¡©®²³·º¼¼ººº»¼¼¸°§¢›“‘–››™–’†„„…3"mzvu)E€…‰0*…Ž‘’‹,X¢¢¦§¨§®s$#'&5¡¸±±³¶¸¸¼r%%%#$T®¨¨§¦¦¤¤¢¢¢¡C!Q’‹/(„““@ f«¡¡¡ž››››˜¤w'$R«¿ºÁÀ½»¾¾¼³¤“ŽŽ˜£«±²²±³¶¸»¹³­œ‘—£­´µ¶¸¹¹¸°«¯­¡ˆ€Q - - - &$$%&*///0/-,,-45,++++++++**)))()*)*'&3/000.10038766777596987! &˜ÃÉÏÏËËËÇÃÁ¾¼º¯ –•ž¦¬°´¶¸»¼º¹º¼¼¼º´«¢–’’˜š›™—•ކ‡‡2!o~{}\f‚ŠŽ3+‡ŽŒ‹ŒŒ‘\$|”—›žŸžž’* #j±§ªª«ª«±m""C¥ žžœšš˜™œ›> T‹Œ’4!}‘•_ '~ª Ÿ››š™— ”1"'$D¾¿¾º¼À¾¹¬˜ŽŒ“§¯±±±²´·¸¹·±¨—˜¥­³·¶¸¸¸¸°¨¯±©’€r - - "&$$&(,//0/.--,-.72++++)*(*))))))))**((6324301128;667:88:=8:9:( - =¯ÄËÐÐÌËÊÆÃÀ¾½»²£˜”œ£ª¯´¶¸¹º¹¸¸»½¼¼º°¤›—’“•˜šœš˜•‹‹8"sƒ€~32„‡Œ‘1*„‹ˆ†…ˆ…25‚”˜œŸ¡šžQ2™¢¢¢ ¡¡¤o/Ž“’‘Ž‘–˜“7_ŒŒ’: y’•„'#.~§žšš™– œ='&(&~û¸¹¿Á½¶¥“Ž˜¢«±²±±³µ·¹¹µ¯¥–‘𥮳¶·¶¹¹¸´«°³¬œ€„5 %''&&),...-----,,091,,.,*)**())))()++**<8798549<9:6::<<=AB?@A@7 -  f¹ÄÍÏÎÌÍËÄÂÀ¾¾¼´¥™•›¡§®²¶¸¹¹¹¹¸»¾¾¾»³©—“‘‘’•˜š›š—•’’:!x‹‡ƒŠa^‘“–‘0(€Ž‰‡ˆ‡Žd(6@JOT[`cIh¡—–—™˜›z]‡‚„…‡ˆ‘”–’1_ŽŒ7 v–•žK! )qœ¡›ž¡‹>&%&%9®¼µ¶»Á¾¸­š”𦮲²²²´¶¹º¹¶­£’‘œ§®²µ¶·¸ºº¶­¯³°£‹ˆ[ ((*))+,///.-,-.-,,27.,,-,++*)))))()**)*<<=>AB@CEEFEHHGO - 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“𢍮²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘І}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(();<DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnЇ+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k - *()((&).0/.-././12:82270,++++++++*))*+**BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’𥬲¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+-,SQRSRQPWWWRQQTVUSRTUWVSUTTW9 - PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—𣍬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**+++*[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄÆÂ¹±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†M00.,,00//.-,-./-+,-/0372..--,*,-051-)***PRRWPPRSSTX[RPSRSQROLNPRQPPPNƒÐÌÌʹ¯¼ÉÆÂÃÌÔÖÖÕÒÍÇÅÆÇÍ×ÚÓÆ²š”’˜¢ª±¶»½¿¾¾¾¾ÁÁÁ¿½¹·³­©¦£  ¡ ¡¡¡¢ žœœœœœžžž¡¢¢   ŸŸœ˜–“”••”’‘‘’•˜œ¡¦©®²·º¾ÁÂÃÄÄÄÄÃÂÀ¾½¾½»¹·¶µ´´¶·µ³¯ª¢™ˆ„ƒ€€ƒ„ƒ…†††ˆ‰“™¤¨«®´»ÁÅÇÆÃÁÅÇý²¦›””””‘“Ÿª´¶¶¶µ´µ¸½ÁÄÄÀ»µ¨™Š„‡” ¨¯³µ·º½¾½»º¹¹º¼¼´±³µ¸¹ºº²žŠg/31,,11//0/-,...-,,..132/.--+,,-,/3.+**)TXSTQQSTWVVXPNRTTQSSPNOPNOQNT0 C»ÌÌËŵ±ÀÉÇÂÂËÓÔÕÕÐËÆÃÄÇÌ×ÛÕȶ–’”¥­´¸º½½¾¿¾¿ÁÁÀ¿¾¼»·³®«§¤¤¢¢££¡ ¡  ŸŸ  žž ¡¢£¢¢¡¡¢¢¢¡ ›˜–••––—˜™š ¡£¥ª¯³¶¸»¾ÀÂÃÃÃÃÁÁ¿¾¼º·¸¶³±¯®®®¬«¨¨¦¡œ–‡€€ƒ…„‚‚…„†‹‘•𤍫¯´ºÀÆÉÇÃÃÄÅÄ¿´§““””’‘—¥°µ¶¶µ´³·½ÁÃÿ¹°¡‘…ƒ‹™¤ª®´µ¹»¼½½»¹¸¸»½¼±°´µ¹º»ºµ¥‹Œ~3*1+,/.////.-,-/-----./01-,,--,,+.030*'-UXSRSRWXZVVXPOQSTRPQRQQOOQPPY@ }ËÉÌËŲ²ÆËÈÄÀÉÓÕÕÔÐÉÅÃÅÈËÖÜÕʺ¢—“Ž‹˜¡ª¯µ·»½¿¿À¿ÀÀÁÀÀ¾½º¶³­¬«§¥¥¤£   žŸŸ Ÿ  Ÿ¢£¤¦¦¥¤¥¤¤£¢¡Ÿ›šœœ››œ ¢£¦ªª¬°µº½ÁÁÂÂÂÃÂÂÁ¿¾½º·¶³²³°­ªª«ª§¢ ›˜“‡„ƒ‚‚€~ƒ…ˆ’˜›ž¢¦ª¯´»ÁÅÉÈÄÂÆÇž¶©–’“”’• ­´¶µ´´³µ»ÀÁÃÄÁ¿¸«›Š‚„‘Ÿ¨ª®³·º»¼¾¼º¸¸¸»½º±²¶¶¸ºº¹¶ªˆŽF"//-.00.-..,-./.-,+-/02/,+,*+,,./47.+,TSQQSVTRWSUUPPPQPONPQSSSTUSTYM.«ÊÊÌËÀ¯µÆÊÈÅÁÇÓÕÖÔÏÉÄÂÄÇÍÖÚÕ˼¤–”‘Ž‹Œ’¦¬´¸»¼¾¿¿¿ÁÀÀÁÁÀÁ½¹¶±¯±­©¨§¦¥¢¡žžŸŸ ¢¡¢££¤¥¤¤¤¤£¤£¡¡ŸŸ¡  Ÿ¡¢¤§ª®±´·¼¿ÃÅÅÄÄÂÂÀÀ¿½º¸¶´±¬ª¨¥¤£¢£ œš˜Šˆ…ƒ‚ƒ€}}~‚†ˆ‹’–› £¥¨ª¯¶»ÂÇÉÉÅÂÅÇÄÀµ¨›—”“““Ž“žª²¶µ´³´¶º¿ÂÄÄÃÀ¼µ¥‘…„—£©«®³¸¼¾¾¾»º¹¶¸½½º¯²¶·¸¹¹¹¸®”†Ž_.,,-/.----,--.-,++,.00,*++*,,,,/42-+RPMRUSPQSTSRSPPONRSOPQRQRTQQQS)eÉÉËÌʼ­·ÆËÈÄÁÅÑÕÖÔÍÇÃÃÄÇÍ×ÛÖÍ¿¨™•’ŒŒ˜¡¨°¶º½¾½¾¾ÀÀÁÁÁÁÁÁ¾º¶µ´±­¬«ª©§¥£ ŸŸŸ ¢¡¡¢¢¢  ¡¢£¥¤¤¢ Ÿ ŸŸ ¢¢¥¥¨ª®±µ¹»ÀÃÄÅÆÆÅÄÃÄÂÁ¿½»·µ³±°®©¦¡žš“’‹„}|}„ƒ}~ƒ‡‘–𢤧©¬°µ»ÁÇÊÊÅÂÄÅü²¨œ”“””“’œ§°´¶´³²µ¹¾ÁÂÄÿ¼²žŒƒ‚Žœ¥«¬­³¹½¿À¿»¹¸·¸½½¹¯µ··º¼¹º¹±žŠŠv$,-,../...,,,-..-*+-/.++**,-,+++.4/+SPQUTRVVSSTSTQQRQSQOQNQOQROOOQ=(œÇÇÊËÆ¹«ºÆÊÈÄÀÃÎÒÔÒÌÇÄÃÅÇËÔÚ×Ï«œ–•’Œ“›¤«²¶¹¼¾½¾ÀÀÁÁÀÂÂÁ¿»¹¸¶´´²¯­¬¬ª¦¤¡ ¡¡¢¡    Ÿ¡¢££££¢¡ ¢£¡¢¡£¥©¬®²µ¸»¿ÂÄÅÇÇÈÆÅÅÄÃÂÀ½º·´²°®®­«§¢›–‘Žˆ†~~……ƒ€„†‹‘•–™œ £¦¨©¬°³º¿ÆÈÉÅÂÃÅû±ªŸ–““”“ŽŽ—£­³µµ²²´¹¼ÁÂÂÂÁÀ½¹®š‰ƒ‡•¢¨¬¬­²¹½ÀÀ¾¼¹·¶º¾¿¸°³¶·º¼»»ºµ¤Œ…€=).////0..----+++,-.,*,,*,,*,,*,44- \ No newline at end of file diff --git a/libs/ultrahdr/tests/data/minnie-320x240.yu12 b/libs/ultrahdr/tests/data/minnie-320x240.yu12 deleted file mode 100644 index 0d66f53029..0000000000 --- a/libs/ultrahdr/tests/data/minnie-320x240.yu12 +++ /dev/null @@ -1,1930 +0,0 @@ -ØÖÖÓÑÑÏËÈÈÈÅÅÃÃÂÅÂÁÀÁÀ½¹¶³°«¬±´³²±´¯–‘ž°¯¯±´³³´µ¹»½½½»¸¸·¹»¼½¿ÁÃÆÇÉÌÎÏÐÎÍÌËÊÊÉÊÍÌËÊÆÇÈËÊÌÌÉÈÇÆÅÄÄÅÆÇÆÆÇÇÆÅÃÄÂÃÁÁÁÁÁ¿¾¼¼¼¼½»»¸·¹¸·¶µ³²²²²²±°¯¯®®®ª©¦£¤¥¤“}vrswqnpsqqklspmjmnlknw{’™›œ›š›››™›ššœœšœžŸ¡¢¤§«¯³µ¸ºº¼¼»º¹µ²­¥ž—”’”¦®´µ·¸¹º»»¼»¼¼½¾¾¾½¼º¹¸¹º»¼»½ÀÂÃÆÊËÊÊÍÍÌÌÉžºµ±­©©­´¶¶¶³²²³°¬©­²³¯©¢¡¨±´·µµµ³­›…„•¬¸º¹··³¨ ¦¯±£W9ÙÙ×ÕÒÑÏÎÉÇÇÈÅ¿¿ÁÄÂÀÀļ·´±­¬«®±²°­°§Ž›­®¯¯°²²²µ¹»½¼¼¼º¸¸¹»½¾¾ÁÅÇÆÉÍÏÐÏÎÎÌËÌÌËËËËÌÊÇÇÉÊËÌÌÌÊÉÈÈÆÇÇÇÈÇÇÆÆÆÆÆÆÄÃÃÃÂÂÁ¿½¼¼½½¾»¹¹¹¹¹·¶¶µ´µ´²±²±¯¯­­®­¨¤¢¥¤f96999:7464336753/01,..2344N“žœœššœœœœžž¡£¦«®°³µ¸»¼½½¼¼¹´®¨¡›•”–𤫳¶¶·¸º¼½½¾¼½½¾¾¾¿¿½»¹¹ººº»¼¾ÀÂÅÈËÌËÎÏÐÎÊÆÄ½¸µ±¬¨§¯·¶µ´²±²²°­©®²³®§¥¥«°³¶¶µ´¯¨“‚ˆ¡³¶¸···³¨¡§°´²w:ÚÚØÖÓÒÐÐÍÊÇÇŽ¾½¿ÁÁÁÃÄÁ»µ²¯®ª«°°¬¥£¬“¨­®¯°±°±µº½¼¼¼¼»¹¸¹º¼½¿ÁÅÈÉÌÎÐÐÎÍÍÊÊÌÍÍÌÌÌËÉÈÈÈÊËÌËËËÊÊÉÈÈÈÇÆÅÆÆÆÆÆÇÆÆÄÄÃÂÂÁ¿½¼½½½¾¼¹ºº¹¸·¶µµ´´µ´²±±¯¯­­­¬¬§¤ªr'Rouwwvzy{zxyzyxtsvurkgce_F U¢žœ›››œœ››Ÿ ¡£¨«°±³µ¸»½¿¾¾½º·°«£›–••˜ ¨®´¶¶¹»¼½½¾¾¾¾¾¾¾¾¿¿¼»º¹¹º»¼½¿ÁÄÇÊÍÍÍÑÒÏËÉÄÁ»·´±­§¨²·¶´³³³²²¯«©®²³­¨¤¦°³··¶µ³®£‰–®µµ¶¸¸¸²¤ §±¶·OÙ×ÙØÔÑÐÍÎËÈÅÄÃÀ¿¼º¼½ÀÁÂĺµ²¯®­­®©™”ž£–£ª­¯°°±°²¹½½¼»¼º¹¸¹¹»¼¿ÁÄÈÊÌÎÑÑÏÍÌÊÊËÌÌÌËÌÊÉÉÈÈÊËËÊÊËÊÊÈÉÉÈÇÆÆÆÅÆÅÄÆÃÄÄÁÁÂÂÁ¿½»¼½¾½»¼»º¹·¶¶··´´µµ´²±°°®­¯®«ª¥¤@: ¬ª¬©ª¨§§©ª§©¨§©¦£¦¦£•¥£¤•;:—Ÿžœ™š››œ›œœŸ£¥§«®±´·»½¾¿À¿½»¸³¬§Ÿ™•”–ž¦­°³¶¹»½¾½¾½¾¾¾¿¿¿¿¿½¼»º»º»¼¼½ÁÂÇÊÊÌÏÑÑÐÏÌȾ¹´¯¯¬§«´¶¶´µ¶³²±¯ª¨­³²«¨¢©¶¹ºº·¶²¬™~‹¢°±²µ·¹¶®£ ¦±··¯oÛ×Ö×ÕÒÐÌÍÊÇÇÅÁ¿¾º¸·¹¼¿Á¿¹µ²¯®®°«—‰‘œš¡©ª­®²²°³º¼¼¼»»º¹¹¸¸¹¼¿ÀÄÇÈÌÎÐÑÏÎÍËÉÊËËÍÍËËÊÉÈÈËËÊÊËËÊÉÊËËÉÇÆÈÇÇÆÄÄÅÄÄÄÃÃÂÂÁ¿½½½¾¾½»º»»¹¹¸¸¹º·µ¸·¶¶³²³°°°°®­ª 5M¦¡¢¡¢¤¤¦¥£££¤¤¢  ¡ £}>Ž¡ž¥I1‘Ÿœš™™›ššš›œ›œ £¦«¯²´·º»¾¿¿¿¾½»µ®©£›˜–•›£©¯³¶·¹½¾¿¿¿¿½½¾¾¿¿À¿¾½»º¼»¼¼½¿ÁÃÇÊÌÎÏÑÑÐÎÊÆÁ¼¶±«ª¨¨­³µµ´´´´³²¯©§­±®«¦£­´¸º¹¶´°¤‰€’§«­°³¶·²­£ §±¸¹²•ÛÙ×ÓÑÓÏËÊÈÄÄÃÀÁ¿¼º¶´µ¸½ÀÃÿ¹´²°­¬«£ˆˆ”›Ÿ§ª«¬°±¯°º½½½½»»º¹·¸º¼ÀÀÃÆÈËÎÐÐÐÏÍÌÊËÌÌÍÌËËÊÉÇÈÊËÊÊÉÊÊËËÉÈÈÈÉÈÇÆÇÇÆÄÃÄÃÅÄÃÂÂÀ¾¾ÀÀÀ¿½»»¼¼»ºº¹¹¹¸¸¸¸¸µµµ³³³²°¯¬5T§¡£¢¤¥¦¦¦¦¥¥¥¦¤¢¢¡ ¢T1s¥Ÿ¢F)sš›—–————˜™››ž £¦ª¯³¶¸º½¿¿¿¾¾½»¹±­¦Ÿ™•–›£¨®³µ·¹»½¾À¿¾¾¾½½¾¿ÀÀ¾¾¼ºº»»½¾¿ÀÂÄÈÊÍÏÏÐÑÏÌÈÄ¿º´­©§¦§°µ´´³´³³³²¯¨©±±­¨¢¥°¶¸ºº·²¬ƒ‰˜¤¨¬¯²µ¶²­¤¤§²»º²¬ÚØØÕÐÏÎÌËÉÃÁÀ¿¿À¾º·²°²¶»¿Â¼¸´²®¨©ª„Œ”ž©©««®°±°¸»¼½½»»»¹¹¸»½¿ÂÄÆÆÊÏÑÑÑÐÍËÍÎÎÎÌËÊÈÊÊÇÆÉÊÌÌËËÊÊÉÈÇÈÈÇÇÇÇÈÉÈÅÄÄÄÆÄÃÄÂÁÀ¿¿¿ÀÀ¾¾½½½¼»¼»¹¸¹º¹··¶µ´´µ´²²°­™2Z©¤¦¥¦¥¦¥¦¥¥¤¥¤¤¤£¢£‡HeP¡ ¡@&-Hh‰—–’”—ššœ ¤¦©­°µº»½¿¿ÀÀ¿¿¾»¶®©¡œ˜•™¢ª®²¶·ºº¼¾¾¾½½½½»½¿¿À¿¿¾»¼»º¼½¿¿ÂÄÆÊËÎÏÐÒÒÎËÇ»·°«¨¦¥¦­²³´´´´µ´²­¦©¯¯­¥ ¥·¹»»º·°§Š™¥©¬°²µµ´«¡¤©´½»³¯ØÕÖÕÑÌÊÊÉÇÄÁ½»¾¿¿»¸³¯­¯´¹½Á¿»¶²­©§­£…ˆŽ—¥§©«¬®­®·¼¾»¹»¼»ººº»½¿ÂÄÆÇÍÑÑÓÒÏÍÌÌËÌÍÌËËËËÊÈÈÉÉËÌÍÌÊÊÉÈÈÇÆÄÅÅÆÉÉÇÅÄÄÆÅÂÂÃÃÁÀ¿¾¾ÀÀ¿¾½¾¾½»»º¹¸ºº¹¸¸·¶¶¶µµ´³±°–0b­¥¦¦§¥¥¦¦¥¥¤¥¤¤£££¨_gA‘¡š8$A,&6W†—›š›ž ¤§ª®²µ¹»½¿¿ÀÀÀ¾½¼·±©£ž™—™ž¦«¯²¶¸¼½¿¿½»»¼½»»½¿¿¿¿½½»»ººº»½ÀÃÆÉÌÍÐÑÑÒÒÏËÆÀ¹³­©¨¥¤¨¬°²³³´··¶±«¤¨­­©¢ž«¸½»¹·³® „ƒŸ¨«®¯°µ·²§Ÿ£«´»½µ®ÐÌÎÐÒÎÊÇÆÆÅÁ¾¼¼¾¾½¹´°«¬®²·½¿½¶³®ª§¬°‰Œ” ¤¦¨ª©©ª¶¼¼º¹¹¹º»º»½¾ÁÂÅÈÊÍÐÐÒÑÎÌËËÌÌÍÍÌËËÊÈÇÆÆÈËËÊËÊÊÊÉÉÈÆÄÄÅÅÅÆÆÅÆÄÅÅÄÄÄÿ½½½¾¿ÀÁÀ¿½½½»»º¹·¹¹¹»º¹¹¸¶µ´³´³³•/l²§¦¦§§¨¨¨¨§¦¦¥¥¥¤¤—?S_8z¤”06‘}V5$.YšŸž¡¦ª¬®²µ¸¼¼¿ÁÁÂÁÀ¿½·³­§¢™™Ÿ¥¬°±´¸»½ÀÁÀ¾½¾¿¿¼½¿À¿¾¾¿½º¹¹¹¹»¾ÂÄÈÌÍÑÓÓÓÓÐÍÇž·±¬¨¦¤¥ª°²²²³µ··´¯¨£§««¤Ÿ¡±¹½»·µ±ª“‰•¢ª¬¬­²¶¶¯§ ¦­´»¼·­ÂÀÃÈÇËÊÇÅÄÁÁ¿½»»¼¼¹¶²­¬«¬±´º½º´°ª¦§±¨‡‹’¡£§©¨©«´»¼º»¼ºº¹º¼½¾ÁÂÆÆÊÎÑÑÑÐÎÊÊÍÏÍÌÎÍËÊÉÉÈÄÆÉËÌËËËÊÊÊÊÉÉÇÅÅÅÄÄÄÄÅÅÄÄÅÅÅÃÀ¾¼¼¼¾¿¿¾¿¿½½»¹¹¸·¸¹¹»ºº»¸·µ´³´³´”0xµª§¥¥§¨¨¨¦§§¥¤¦¦¤¥vXzvT^ ,C¢¤¡ˆ_;.2Mu•¥¥ª®²µ·»¼½ÁÂÂÃÂÀ¿»¶¯©£¡œš›£¨®²´¸»½¾ÀÀÀ¾½¾¾½¾¿ÁÂÁ¿À¿¾»º¹º»¾ÀÃÈÊÍÐÓÕÕÔÒÎÊÅÀ¹³®¨¦¥£§¬¯±±²²µ¶¶³®¨£§«¦Ÿ›¦µ»¼º·²­¢†ƒœ§­­­¯´µ´¯¤¡§­µ¼½¸°¹µ³¼ÁÄÆÈÆÃÂÀ¾¾»ºººº¶³±¬©¨©®´¹¼¶±¬¨¥§±—‰™ ¢¦©¨©«³º¼½¼»»»ºº½½¾ÀÀÄÇÉÎÑÑÐÑÎÌËÌËÌÍÍËÊÉÉÈÉÆÆÈËÌÍÍËÊËÌËËÊÉÈÉÆÅÅÃÄÅÆÅÃÄÄÄÃÁ¿¾½¿¿À¿¿¿À½½¼ºº¸¸¹ºººº»»¸¶¶´´µµº“2‚¸®«¦¤¥¦¦¦¤¤£¡£§¥¤¤qЧ¦ŒW™ƒ'LŸ›¡¡ŽjI93Cjž­°µ¸»¼¾ÀÃÃÂÁÀ¿½¸³­¦¡ž››ž¦«°´¶º½¾¿À¿À¿½½¾½¾ÀÂÂÁ¿¾½¼º¹»¼¿ÂÄÇÌÎÐÓÕÔÔÓÏËǼµ°ª¥¢£¤©®²³´³µ¸¸¶³®¦¢¨ª¢™š«¸¼»¸´°ª˜€…“¢ª®®¯³²µµ¬¢¡¥¬¶¼¿º²¶´²±·¼¿ÅÆÄÂÁ¾»½¼º¹¸µ²²¯«§¦¨¬¯·¹´®¬§£«®Š˜ ¢¤¨ªªª³º¼¿¾¼»»»»¼½À¿ÀÁÅÈÍÏÐÐÐÍÌËÌÌËËËËÉÊÊÉÈÇÇÇÉËÍÍËÊÌËÊÌËÉÉÉÉÅÅÅÅÆÆÅÄÄÂÃÁÀÀ¿¿ÀÀÀÀ¿¼¾¿¿¿½¼ºº»ºººººº¸¶¶µµ´´¼ˆ3ˆº°­ª¦¦¦¥¦¤¢¡Ÿž£¤¤£ £Ÿ››ƒ$W šš› ¢žŠmR.T­¶¶»½¾ÁÃÄÃÂÁÀÀ»¶±«£Ÿœ›ž¤«¯³¶¸»½¿ÁÀÁÀÀ¾½¾¿ÀÂÂÃÂÀ¾¼¼¼»¾¿ÁÅÇÊÍÐÒÓÓÔÔÐÍÈÿ¹±«§¤¢¢¤¨¬°³¶¶¹¹·¶³¬¤£ª§¡™¡²·¹¹¸´°¦†ƒ‡˜¤«®¯³´µ¸´«¢£§­¸¾¿½²º²°¯±µº½ÂÃÃÁ¿»»»ºº¹¸³±¯¬§¥¤¥§¯³¶³¯«¦¨´ª‡”¢¥¦©«©ª±·»¾¾¾¼ºº¼»¾¿ÁÃÃÆÈÍÐÏÐÎÌËËÌÌËËÊÊÉÊÉÉÈÇÆÇÊÊÌÌÊÈÊÉÊËÌÊËÉÈÇÇÇÅÄÅÅÄÅÄÄÂÀÀ¿ÀÀÀÀÀ¿½½¿¿¾¿½º»»º¹¹¹¸¸¸··¸¶´²¼5Œ¹°®­«ª©§¥£¦x𣢤£¤£¡žœœž€ ^£œœ››i‡­¬¥X/˜º¹¼¿ÁÁÂÄÃÂÁÁ¾¹³®¨¢œ›¤ª¯²µ·º¼¾¿ÁÀÁÀÀ¼»¼¾¾ÀÁÀ¿¿½½½¼¼¾ÁÄÆÊËÎÐÒÔÔÔÑÎÊÆÂ¾·¯©£¢¤¥¦ª®°´·¸¸¹¸µ°«¥£¨¦œ™©´·¹¹·³ªš€‡œ¨¬­°´´¶·²¨ ¤©¯ºÀÀ¼°Á´¯°°±µ»¾¿ÀÀ½»¹¸¹»º·¶³°«¨¤ ž ¤«²·µ­¨¦¬¸™Œž¢¥©«ª¨®¸»¼¾¾¼¹»»º»½ÀÃÅÇÊÍÎÍÎÏÍÊÉËÍÊÌÌÉÉÊÊÊÇÇÇÈÊËËËÊÈÉÉËËÌÍËÊÉÉÇÅÄÃÅÄÃÄÄÄ¿¾¿¿À¾¾¿¿¿¾¾¾¾½»¹ºº¸¹¸¸¹¸¶¶¶¶¶µ±¹s2’·°¯­¬«©¦¤¦—G#L—¥£¤¤¤¡Ÿžœt g¤›œœŸi1t¯¨³o0”¼º½¿ÀÁÁÁÁÁÀ¿º´°ª¦ œ¢©®±´·¹»¿¿ÀÁÀÁÁ¿»º»¼¼½¾¿¾½½¾¾¼»½ÁÄÆÊÍÐÓÓÕÖÒÎËÇþº²ª¥¢£¦¦¨¬°²¶¸¹¹¹¶´®ª¢ ¤ —°µ¹¹·¶°§Ž‡”¢«­­°³¶·¶°¤Ÿ¤¨°»ÁÁ¹±Éº±°±°²µº¼¾¿¿¼¸··¹¸¸¶µ²¯©¥žšš¡§±¸µ¬¨§¯°‘›¢¥§ª«¦¬¸¼¿¿¾½»»¹º»¼¾ÁÄÇÊÌÍÍÏÏËÉÈÈÊËËËÊÉËÊÉÈÆÈÉÉÊÌËÉÉÊÉÊËËËÊÊÊÊÈÆÆÄÄÄÄÄľ¾À¿¿¿¿ÀÀ¿¾¼¾¾¼ººº¹¹¹¶¶¹¹·µµ¶µ³±¸j.—´°¯®­«¨¥¥›B!)#JŸ¤¥¥£ žŸž n"r¤£zOXw±¬²Z;¬½»½¿ÀÂÁÁÂÁ¿»·²¬§¢Ÿœž¤¬¯³··º½ÀÀÀÀÁÁÁ¼»¼¼»½½½½½½½½½¼¼¼ÀÄÇÊÍÑÔÔÕÓÏÊÉÄÀ¼µ®¥¡¡£¦§©«¯²µ¹º»¸¶³¬¥ Ÿ œ™¨¶¹¹¶´±¬‚‚Œ™¥©¬®²¶·¹´­£ £«´¾À¿¸¯Ïµ²±°±´·¹º¾¿½¹¸·µ´¶¶µ³²®¨¡™—˜š£¯¹¹¯¬­¶¢•££¦ªª¨¬¸¼½¾½¼½º¹»¼½¾ÁÄÇËÌÎÐÑÏÌÉÊÉÊËÊËËËÉÈÉÈÇÈÉÊËÍÌÉÊÊÉÊÉÉÉÈÈÉÉÊÉÇÅÄÃÄÄÃÁ¿¾¾¾¿ÀÁÀ¿À¿¿¾¾¼»»»ºº¹¹·¸»»·¶¶µ³²±¶b/𴝱°°­©§¥M'()%dª¤¤£ ¡¡Ÿ¢j"}¥ž¤‹EŒlwµ²¥?`½½¿ÀÂÃÂÁÀ¿¼¸´­©¦ žž¢¨¬²µ¸¹»¾ÁÂÁÀÀÀ¾»»½¾¼½½½½¼¼¼»»º½½ÀÃÈÌÎÒÓÔÓÐÍÈÅÿ¹±¬¥¡ £¥¦©¬°´¶¹º¹¹¶´¯¨Ÿœœ˜°¹»¹·´¯¥Œ‚ˆ‘ž¤©¬¯´¹¸¸´©¢Ÿ¤®·¾Á¾¹°ÏȺµ´²´µµµ¸¹»»º¸¶µ´³³µ²³°«¦ ™–““›¡¨µ»·³¶¶› ¢¥¨©¦ªµº¼¾½»»º¹º½¿ÂÃÅÉÍÎÎÐÐÎÌÊÊÊÉÉËÌÍÍËÉÊÈÈÈÇÉËÌËËËËÌËÊÉÈÈÉÊÈÈÉÇÆÃÁÁÂÁÁ¾½¾¾¿ÀÁÁÁÁÀÀ¾½¼»¹¸¸¹¹¹º»ºº·µ¶µ³³³·^4³®°°°¯ª¬u%&&'&/•¥£¤££ Ÿ¥e'…¥¦•IEzXy´·|5•ļ¿ÀÂÃÃÃÂÀ¾º´®©¤¡ŸŸ¢§¬²¶¸»¼½ÀÃÄÂÀÀ¿½º»½¾¾¿¾½½¼¼¼¼»»½¿¿ÃÊÎÐÓÓÒÐÍÈÅÃÀº³®¨¥¡¡£¤¥©¬±¸º»»¹¹¶³­¥›š™£³º»¹·³«œ‚‚‹–¢¦¨­±¶·¹¸±¤¢Ÿ¥°¹¿Á¿»°ÐÍÀ·¶¶¶´µ¶¶·¸¹··µ´µ³²²³´³°«¥Ž’–›¥³½¿¾¹¦› £¤§§©´¹»¼¾¼»¼¼»¼¿ÃÄÅÊÍÎÏÐÐÏÍËËÊÉÉËÍÎÍËÊÊÈÈÈÈÇÈÉÊÌÌËÌËËËÊÈÉÉÈÇÇÇÅÄÂÂÁÁÀ¾¾¾ÀÁÀÀ¿¿ÀÀÁÀÀ¿¼¹¹ººººº»º¸¶´¶µ´´´¸[9¡±®¯¯°®«¥B%)'&'++y¬¤¥¤£¢Ÿ¥^-§¦dm…g@}¸µPJ¹ÁÀÁÂÃÃÃÄÂÁ½µ°ª¦¢¡Ÿ¡¥ª®´·º¼¾ÀÃÄÄÃÁÁ¿¼»¼¾¿¿ÀÀ¾½½¾½½½»»½ÁÅËÏÑÔÔÑÏÊÆÃ¿»´°«¥¢¡¢¤§§¨«²·ºººº¸µ²«£›™—™©¶¹º¸³¬¦“…™¤§ª¯³¶¸º·¯¦¡¢©³º¿ÀÀ»¯ÑÏĸ··¸·¶µ´´¶¶µ³³³²³³²²²´³¯¦¢”‰‰Š‘•ž­½Æ¿«˜ž¡£¥¤§²·º»½¾¼½½¼½¿ÂÅÇÉÌÍÏÐÑÎËËÊÉÊÊËÌÌÊÊÊÊÊËÊÊÉÈÉËÍÍËËÌËÌËÉÈÉÊÈÈÇÅÄÂÂÁÁÁÀ¿¿ÂÃÂÂÁÁÁ¿ÁÁÁ¿»¹º¼»»º¹¹·µ´¶·¶´´´¶X>£¯®®®¯¯­Ÿ9,+)))0.v¬¦§§¥£¢§Y2–¨¢—®±µq‚½Ÿ8}ÇÂÁÂÃÄÄÄÃÁ½¸±­©¦¢ Ÿ¥©¬°³·¹»¿ÃÃÅÃÂÀ¾½»»½¾¿ÁÀ¿¾¼¾¿¿½½¼¾¿ÄÉÍÏÒÓÒÐÎÈÄÁ¼¶±¬§£ ¡¤¦§§©ª²·¸º¼¹·´¯ª¢™–“ž±ºº¹µ±«œ„‚‡’ž¦§¬²¶¶¸¸µ¬¤œ£­µ¼ÀÀ¾¹°ÒÒʺ¹¸»¹·´³³³²²¯¯±³³´³±°±³²¬§ –Іˆ‰“•Ÿ­´«—™ ££¢¥¯¶¹»½½½½¾¿ÀÀÁÅÈÉÊÌÎÎÏÌËÊËÊËËËÊÊÈÉÈÉÊËÌÊÈÇÉËÍÎÍËÌËËÊÉÇÇÉÉÈÇÅÅÅÃÁÁÂÀ¾¿ÂÄÃÃÂÁÀÁÁÁÁ¼»ºº»½»¹¸¸µµ···¶´µ¶µQB©¯­«¬­­¬ªZ04*+57F—§¦§¦¥¥¤§T6­ª®­­³”›½lA®ÃÀÀÁÂÃÃÂÁ¾¹´­¨¤¢ Ÿ¢¦«¯²´¶¹¼¿ÂÄÄÃÀ¿¼¼»¼½½¾¾½½¼»¼½¿½¼½ÀÄÇÌÏÑÑÐÏÎËÈþ¹³®©¥¡ ¢¤¦¦§ª­³·º»º¸¶²®¨¡™“•§¶»»¹³­¨’€†‹š¢§«°µ··¹¸²§š¢°¹¾¿À½¸®ÏÒξ¸º»º¶µ´³²³±­¬®±±³²°¯®®±¯¬©Ÿ‘†„‡ŠŒ•›—–ž££¢£­¶º»¾¼¼¼¼¿ÀÀÂÄÈÊËËÍÎÎÌÉÇÉÊÉÊÉÊÊÉÉÊÊÉÊÌÌÈÈÉÊÌËÍÍËÊËÊÈÈÈÇÉÈÆÅÅÄÂÂÁÂÁ¿ÀÀÃÃÃÂÁÁÀ¿¾¿¿¿¾½½½¼ºº¹··¸¹¹¸¶¶µ²LI®®¬¬­­­ª®œme=1bm¥£¦©¨¦¤¤¦O<¤¯¯­¯±²³·ªAjÆ¿ÀÀÁÂÂÂÁ¿¼·±«§¢¡ŸŸ¡§­°³¶¸º½ÀÁÂÂÀ½½»ºº¼¾¾½»º¹ºº»»¼½½¾ÂÅÉÌÏÑÒÏÍÍÊǽ¶¯ª¦¢ ¡¢¤¨¨¨«°µ¹º»º¸µ±¬¦ ˜“¯¸¸¸µ±©Ÿ…‚Š–Ÿ¦©­±¶··¸µ©Ÿ˜ž¦°·¾Á¾·¬ÏÐÏź¼»ºº·¶³²²°«©ª­®®¯®­«¬®°®¬§‹‚ƒ…„‡”–‘œž¡ ¡¨³¸»¼»¼½½¿¿¿ÁÅÇËÍÎÎÍÍÊÊÈÉÉÊËÊÉÉÊËÊÊÊÊÌÌÊÉÉÊËËÌÍÌÊÊÊÉÈÈÈÉÈÆÃÂÁÁÁÁÁÀÀÁÀÂÁÁÁÀÁÁ¿¾¿ÀÁ¿½½½½»»»º¹·¹¹¹·µ²¯FO°®­®°¯¯­¬°³¤H7–­£ ¥¥§§¥£¦ªLB«±±°±²³µ½‡:›Ä¾ÂÃÂÃÄþºµ°ª¦¢¡ ¢¦«°³¶·º¼¿ÀÀÁ¿½¼¼¹·¹»»º»¸·¶¸¹º»»¼½¿ÄÇÊÌÎÐÑÐÍËÈÄÀ¸±­¨£ŸŸ¡¢¥¨§¨¬´¸ºº¼»¸µ±¬¦ž””¦µ¹·µ²¬¤“‚…–¡¨¬¯µ···µ®¤œš ¨®¹¾Â¼µ«ÎÒÐȼ¼»ºº¹·¶µ³®©¤¢¤©«««ªªª­¯¯­ª£—‡€‚ƒƒ‡Œ‘Œ”›œž¨²·»½¼»»½¿¿ÀÂÅÇËÎÐÐÏÍËËËËÊÊÊÉÉÉÉËÊÉÈÊÍÍËÉÊÊÉÊÌÍÌÌÉÉÉÈÈÇÈÉÅÃÂÁÁÁÁÀ¿¿¿À¿¿¿¿ÀÁÁÀ¿¿¿¾½¼½½½¼¼¼»¼·µµ´³²±§;Q²¬­®¯­¬¬­¬®ˆ>8}§ £¤¦¨¥¥§ª®IC¯²²±²´¶¸¸XV½À¿ÂÂÃÃÅÂÀ¼µ°­¨¥¢¢¡¤ª­±´·¹¼¾¿¿ÀÀ¿¾¾»ººº»ºº¹¸¹··¹ºº¹»»¾ÂÆÊÍÏÑÐÐÍÉÄÁ»µ±«§¡žž ¡£¦§¨¬³¶¹¹»¹¸¶²«¤ž–¬¶¸¶³°¨ž‡…†š ¦¬´¹¹·¶³¬ ššŸ©±º¿À¿¼´ªÏÏÏÊ¿»¼»¹¸¹·µ²­© Ÿ £§©¨¨¨«¬®°±¬§ ’‚}|€„‰ˆ™™™›§²·»½½½¼½¿ÀÂÃÄÇÈÊÍÑÏÌÌÌÌÌÍÍÌÊÉÉÊÉÉÉÆÈÊËËÈÊÊÊËËÍÌÌËÊÉÈÈÈÉÈÆÄÃÂÂÁÂÁÀ¿¿¾½¾¾¿¿ÁÁÀ¿¿¿¾½½½¿¿¿À¾½¼¸·¶´²±±¥7W¶®¯°±¯­­®®¯—|z¦¢£¥¦§¥¨«®¯GJ³²³³µ¶¶¼›;†ÈÀÂÃÅÄÃÿ»²¬¨¥¤£¢¤©¬¯´·¹º½¾¾¿À¿¾¿¿¼¼»¼¼¼º¹¸¹¹¹º»¼ºº½¿ÃÇÊÍÐÑÑÎËÅÁ½µ°­¨¥ Ÿ ¢¢¢¤¦©®´¶¹ººº¸µ°©£›¡´¸¹µ±­¥‘„Д𠦱·¸¹¸¶±¦ž››¡«³¼¿¿¾¼´¨ÎÍÍ˺¼¼¼¸¶µµ²¬¨¢  Ÿ ¡¡ ¤¨«®°®¬© {|~€†Œˆ‰–™–—¢°¶¹¼¼¼¼¼½¿ÁÃÄÆÈÊËÍÏÍËÌÌÌÍËËËÈÈÈÈÇÈÇÉÊÉÉÉÊÉÉÊÌÍËÊËÊÈÉÊÊÊÉÈÄÄÃÃÃÃÂÀÀÀÁÀÁÁ¿¾ÀÀÁÀ¾ÀÁÀ¿¿¾¾¿À¾¾¼¹¹¸µ´´µ¤5c¹®°±²¯­®°°°²µ²ª¥¥¦§©¨§©¬±°EP¶²²³¶¶·¿oE³ÅÂÃÄÆÆÅľ·¯ª¥£¡£¥¦«°²µ·¹¼¾¿ÀÁ¿¾¿¾¾»»»»»»¹¸¹º¹º½¾½¾ÀÂÅÇÈËÎÑÒÏÌÉÄ¿º±¬©¦¤ ŸŸ¡¢¢£¤©¯³·¸¸¸·µ³­§¡™˜«´¶´²®¦œƒŒ—¤«·¸¹·¸´ª¢™šž¥®¹¾¿¾¾¹²¨ÑÐÒ̹¼¼¼¹·µ´°«©¥¡¡—•——›ž£¦ª­®°±¬¢‹~zz|}€ˆ‡…“”““Ÿ®µ¹º»½½¼»¼¿ÁÂÅÈÊÌÍÍÌËÌÌÌÌËÌËÈÇÈÇÆÈÇÉÊÊÊÊÉÈÉÈÉÊÊÉÉÉÉÉÉÊÊÉÈÇÄÄÄÄÃÂÀ¿ÀÁÂÀ¿¿¾ÀÀ¿¾¿¿ÀÀÁ¿½¾¿¾½¾»¹¹¸¶´µ¶ 6p¼ªª³²¯¬­°°°²¯­©§¨©©©¨¦§«²¯AWº³²´µµº²DlÇÁÂÃÅÇÆÅÃÀ»´¯ª¦¥¤¤§©­±´¶¸¹¼¾¿ÀÀ¿¾¿¾»¸¹º»»ººº»¼¼¼¿¿¿ÂÆÇÈÉÉÌÐÔÒÏÌÇÁ»µ°«§£¡Ÿž   ¢¢¦«°³µ·¸¸µ´²­§ šž®²²±±©¢{‚„Œ”¦²·¸¸¸¶±§žšœŸ¨µ½¿¿½»¸±¦×ÔÓÏȼ¼»··¶´²°¬©¥¢¡œ”Ž”™œ¡£¦©¯°¯® ‹yyy{|~„’ž¬±µ¹º½½¼¼¾¿ÀÂÅÈÊÏÏÎÍËËËËÌÌÍËÉÈÇÆÆÆÈÉÉÉÉÉÉÊÉÈÉËÌËÊÉÉÉÉÉÊÉÇÇÆÇÆÅÅÃÁÀ¿ÁÀÀ¿¾¿ÁÁÀÀÀÀÀ¿À¿¿ÀÀ¿¿¿º¹¸¸·¶¶¹š2yÁ™m¹³²}£±¯°°°®«¬«¬ª©¨§ª¯³­<^¸²²µ¶¶Â8¢ÈÄÄÆÇÇÅÃÁ½¶°¬©§§§§«¯²¶¹ºº¼¿ÀÀÀ¾¾½¾¼»¹º»»»»¼¼½¾¾¿ÂÁÂÅÇÈÊÌÍÐÑÒÐËǽ·±®«¨¤¡ŸŸ Ÿ ¢¤§¬°³´··¶´²°¬§¡œ¥®°°°¬¥™…€ƒ…Ž˜¢®¶¹¸¸ºµ¬¤œœ¡ª¸¿¿À¿»¶®¥ÚÖÓÐʽº¼ºµ´²°®­©¦£¢œ•Љ‹‹“•–ž £§©¬ª§œ†{yz{z~|‡Žª¯´¹¹¼¼½¾¾ÀÁÁÅÊÍÐÒÑÎÌËËÊÊÊÉÉÇÇÆÅÆÇÇÉÈÉÉÈÉÊÉÉÊËÌËÊÉÉÈÈÉÉÈÇÆÆÆÆÆÅÂÁÁÀÀ¿¿À¿ÀÁÀÀÀ¿ÁÀÀÀ¿¿¿ÀÂÁ¿»º¹º¹¸¸¼”.|Á²Ro‚wc²±±²²°®¯¯®®«¬©§®³¶ª;c¶±³·¹½Á]ZÈÅÆÇÈÇÆÂÀ¾º³­©©¨§§«®²¶·¼»¼¿ÁÀÀ¿¾¾½¼»»»»¼¼¼¼¼½¾½¾ÂÄÃÃÅÇÉËÌÎÎÏÏÌÇÁ½·±®«©§¢ž ¥¥¨­±´´´¶µ³²¯«¨¡¦­®®¬¦¢€„†Œ•Ÿ©²¸¹¹¹·¯¥Ÿœ›œ¤®¹¾ÀÀÀ»´«¤ÜÙÓÎÈÀ¹º¹·´±¯­«©¦¤¤ ˜‹ˆ‡ŠŽ“——›Ÿ¢¡¤¢•}yyyxywŠŒŽ™©¯´·¸¹º½¿ÀÂÂÂÆÉÍÐÑÑÏÎÌËÌÊÉÉÈÇÆÆÇÇÈÇÉÈÉÉÊÊÊÉÉÊÌÌËÊÊÊÉÉÉÉÈÇÅÅÅÆÆÅÂÁÂÂÁÁÁÁÂÂÂÁÀÀÂÂÁ¿¿¿ÀÁÁÁÁ¿»»»»ºº¸½“.ƒ¿¼iPsBй²²³²±°±±¯­««©«±³µ¦7f¸²¶¹¼Ä¨A“ÌÄÆÆÆÅÄÁ¾º¶°©§§¨©ª¯±´µ·»»¼¾¾¿Á¿¾¾¼»ºº¹»¼¼¼»º»¼½¾ÁÃÃÄÆÇÈÊÊÌÍÎÎËÆÀ»µ°¬©§£ œ›œž¢¥¨¬¯²³³³´³³±­«¦¡Ÿ§«¬­©£™…€ƒŠ‘›¦¯µ¹¹¸·°¨¡œš›ž§³»ÀÀ¿¾»´ª£àÝÖÍÆÀ¹¶µµ³¯­«ª§¥¤¥¢˜††…ˆ‰ˆŽ’˜—Œ|yyxvsz†ˆ–§®±µ¶¸¼½¾ÁÃÄÅÇÊÏÒÐÏÏÎÍÎÎÌËÌÌËÊÇÇÉÉÈÈÇÇÉÉÉÉÈÇÉÊËÊÊÊÉÉÉÉÉÈÅÅÆÅÄÄÄÁÁÁÁÁÁÁÂÂÃÃÂÂÃÃÃÂÁ¿¿¿À¿¿½¼»¼¼¼»»¸¾Ž/ˆ»¸dž_ª³³²±¯°±²³²¯­««®²³µ¡2h¸²·½¾ÄpRÀÈÅÃÅÆÃÁ¾¾º³®©¦§¨ª­±²µ¶·º½¾¾½¼½¼¼¼ºº¹¹¹º»º¹º»º½¿ÀÂÃÄÅÆÉÊÊËËÍÍÊÇý·±¯¬¨¥¢ž›››œŸ£¦ª®°±±²²²±²±®«¦Ÿ §©ª«¥Ÿ‚…Œ”¡ª²·¹¸¸³¬§Ÿœ›œ¡ª·½À¿½¼¹±©£ÚÙÔËĽ¸¶¶´²°®¬«¦¢ ¤¤ š“‰„ƒ…ˆˆ‡‡‡‚€~‡‹Ž„{yxrlxƒ„Š”¦¬­°´º¼¼¾ÀÂÃÆÉÌÏÒÒÐÏÏÏÎÎÍÍÍÌÌÊÈÇÆÇÈÈÇÆÇÉÉÈÇÇÈÈÈÉÊÊÉÈÉÈÉÈÇÆÆÆÄÄÃÀÀÁÁÁÁÁÂÂÃÄÃÄÄÅÄÄÂÀ¿¿ÀÀ¼»»»»»»¹¹·»Š.ޏµªQa~·±²²°®¯°²³³²°°¯²´´±˜0l¸´º¾Â°EˆÍÄÅÃÅÆÃÀ¾»·²­ª©©ª¬¯²µ·¹»¼½½¾¾¼»»º»»º»¹º¼¼½»»½¼¾ÁÃÄÅÆÇÈÊËÌËÌÌÊÇÿ¸³­©§¦¤ šš›œ¡¥¦ª®¯°²³²±¯°¯­©§Ÿ §¨¨¨¡˜…ƒ‰’œ¤®¶¸·¸¶°«¥ž››ž¦°¸¾ÁÁ½¼·¯¦£ÉÉž»¸¶·´±¯­­ª¦¡¡¢¢¡œ–Œƒ€ƒ…††„€{yxwx~€|vvtlv„‚†”¤««°µº¼½¾¿ÀÂÆËÏÐÒÓÑÏÎÏÎÎÎÍÌÊÉÉÈÈÆÆÇÇÇÈÉÉÉÈÇÈÉÊÉÉÊÈÈÈÇÈÇÆÆÆÆÆÄÅÁÀÀÁÁÁÁÁÃÃÃÃÃÃÅÄÅÄÁ¿¿½¾¾»»»»»»º¹º·¼ˆ-¹¯µ_@¨µ³²²°¯±±²³³³³´µµµ²²+r¸¸¾¾ÇzL¸ÅÃÄÅÅÄÿ¼¸µ²®ª«¬¬¯²µ¹º¼½½¿¿¿¿¿¾¾¼º»½½¼¾¿¿¿½½½¾¿ÂÄÆÇÈÉÊËÌËËËËÉÅÁ¼µ¯©¨¤¢Ÿ›˜™ž¡£¥¨¬¯±±±²°¯­­«©¤ž ¤§§¤Œ‡Œ˜¡«µ¸···´°© š™› ©²º¾¿¿¿¼´ª¤£¼»¹¹¹¼»µµ²°®¬¬ª¦¢¢ žš•†€€ƒ…ƒ€{wtstwvwvtrojq‚ƒ‚¡ªª®´·»¾ÀÀ¿ÂÅÊÎÐÑÑÎÏÎÍÍÍÌÌÌËÉÈÇÆÇÅÆÈÈÉËÊÊÉÇÈÊËÌÊËËÊÉÇÆÅÅÅÅÅÅÅÄÁ¿¾ÀÁÁÁÂÂÁÁÁÁÂÀÁÂÁ¿¾¾½¼½½¼»¹¹»º¹¸¶¹‚-й«­†t³®­®°¯¯°¯±²³³²´¶´³²´Š,~À»¿ÀµItÇÁÅÆÇÆÃÀ¼º¹´°¬ªª«­±´¸¹¼¿¿¿ÁÂÁÀ¿ÁÀ¿½½¿¾¿ÁÀÀ¿¾¾¾ÀÂÅÅÇÉÊËËÌÌËÉÈÇÆÄ¾¹±«©¥¢Ÿœš˜šŸ¡¤¦¦©¬®®¯±±°°®¬¬«£ž¢¦¤ –†€ƒ‰“œ¦°µ¸µ·¶³ª¢œšš¥­·¼¿¿¿½º±¥££³±³³µ·º·°°¯­««©¦¡ Ÿœš˜—”Œ€‚}ytrrrrqoqpmifm‚‚‹Ÿ¬©«±·¼¾¿ÀÁÄÅÈËÎÐÏÏÎÌËÌËÊÊËÉÆÅÆÇÆÆÇÈÈÈÊÊÉÈÈÈÉÊËÌËËÊÊÈÆÇÇÆÅÅÅÇÅÂÀ½¾ÁÂÁÂÂÁÀÂÁÁÀÁÁ¿¾¼½½¼½½»º¹¹»¹¸¶³´’.W§«©¬±°®­­°°®¯±²³´²±±±³²²¼‚2–Á»½Å–:£ÈÅÆÅÅÅþ»¹¸´°­ª©ª­±µ¹¼¾ÁÁÂÃÂÃÄÄÄÄÃÃÃÁÀÁÂÁÀ¾¿ÀÁÄÅÇÆÇÈÊÌËËËÊÇÆÄ¿¹²¬¨§¤¡š˜™›œŸ¢£§ª­®®®¯²²°°®««§£››Ÿ¢¡™‚‡™¡¬´¶¶¸¶³­¤žœ›ž¨²¹½ÀÀ¿¼·®¤¢¢®®¯°²µ¸·±­­«ª©©¥Ÿœžœ˜““„}€}vtrppnkkmkihch{€ƒŠ›«««¯´¹¼¿ÀÃÅÇÊËÍÏÐÏÌÍÌÊÉÉÈÉÈÅÅÅÄÅÇÈÈÇÈÊËÉÇÇÈÉÉÊËÌËÊÉÇÉÈÈÈÇÅÆÆÅÂÁ¿¿ÁÂÂÁÁÁÁÁÂÃÁÁ¾½½¼½½¾¼»ºº¸º»¹¸¶´²¯T'BWcltz‡”› ¥§©©®¯°¯¬¯®® RI³À½½ÆmLÀÅÇÆÅÄÿ¼º·µ²®«ª­¯²¶»¾ÁÄÄÅÇÅÅÇÇÆÇÇÆÅÃÁÂÅÃÁÁÂÂÄÅÅÆÆÈÇÉÊËÊÉÈÅÅÁ¾º´®¨¥¦¢Ÿš™˜šœ £¤¨¬®¯°¯®°±°°®¬©§¢™œ ¡›ƒ…‡’¦²·¶·¶³®¦¡ž››¢¬´»¾ÀÀ¾º´©¢¢¥®®®®¯²µ¸´­«ªª¨¥ ™š››˜“‡€}~~|zxuromkjihggeaev~‚Š›¥§ª­±µº¿ÀÃÅÉÌÎÐÑÑÎÌÎÍËÊÉÉÈÇÅÄÄÄÄÆÇÉÈÆÉÈÆÆÇÈËËËÊËÌËÊÈÊÉÉÉÈÇÇÇÇÃÂÁÁÂÂÂÂÁÀÀÂÂÂÀ¿¼½½½¾¿¿¾ººº·ººº»¹µ²µ¦eD;7630*0348;X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¥ª¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdЧ¶¸¹¹º»½ÀÂÂÆ¯EŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥§¬»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£¨¯»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:?:–ÖÌÍÍÍÌÍÊÊÒºl>VŸ¢†IFHLMKMLOT]ds‹‹‘—š›Ÿ¢¥§ª¬¯±´¶¸¹··¸´¯¬¨¥£¡ žœšššš›››œœžž¡£¤¦¦¦¨©©¨§§¦¦¥¥¥¤¢Ÿ›’|{¡ª©ª««¬¬«ª««­­­­¯¯¯°²·¹¸·³­¨¥¡¡¢¦©©©«««™Š†„}z{}~€ztqppqrrsvuxxz~€‚‚ˆ•˜›œž  žŸ™‚s_RQaq~Œ”˜›—’“•––˜™ž£¤¤¥¥££¥¥¦¥§«­°²µ¸¹»»»¼¿¿ÁÃÄÅÆÆÇÇÇÈÌÏÒÒÑÐÏÎÏÏÐÐÏц;C>>>¦ÐËËËËÊÊÎÊ•E-:<=9QžÎÈÁÆi'..4>IVfs”˜››’‹‰¹ÄÃÉ—/*'&(&'$"!!6›š™¡a!9q•›œž £¥§«­®²µ·¹»º¹¸µ°«¨¦£¡ŸŸ›››œœœ›žŸ¡£¤¥¥¥¦¥¦©§¥¥¦¥¤£¤ Ÿœ˜ƒ™©¨©ª¬«ª«ªªª©©«««ª¬­­°³³²¯«©¦£ŸŸ¢¥¦¨©©««•‹‡ƒ|xxz{}~~ytpopppqrsttvwwx{}}~‚ˆ††‡ˆ‰€vfVO[n‡’–—™–‘ŽŽ””••”–šžžŸŸž ¡¢¢¢£¦¨ª­°²¶¸¸¹º¼¾ÁÂÄÅÅÆÇÆÆÈËÏÑÑÐÑÏÏÏÏÏÎÍÐw:DA?@­ÎÊÊÊÉÌÒ¯c.3<=?:h¸ÑÉÄÀÄc243310.--/1/1552215¨ÇÂɈ140.,*(''&#""!!  9˜™–›Z " EŽª£§©«®¯±²³·¸¸¹¸·µ°­¨¥£¡¡Ÿš™šš›œš›œœŸ¡¤¤¥§¨¨§§¦¥¥£££¤¦¥¥¥¡Ÿš•€’¦¥§©ª«ª¨¨§¨¦¤¦§§¦¦¥¨ª­­¬ª¨©¨£žœœŸ£¦¨©ªªªš—†€~|ywxyy{}{uqmlmmmnooppopqsrsvvrutqqqrrnnj_UKTl‡””•––’ŽŽ‘’’’”——™™›  ¡£¥¥¦¨©¬­¯³µ·º¼¿ÁÅÅÄÅÆÈÉÊÍÐÑÑÏÐÎÎÏÎÎÌËËk;B@=C´ÌÈÊËÑË„80:=>9B…ÈÑÇÆÃÀÆ^43225689:9877778:<@®ÄÂÊ~.1/+*)'%$#!"! "! @™˜™N!#$%-†¯ª­°±³µ´µ¶¹¹¹¸¶²­©¦¢¡¡žœšš›š››™—˜™œ ¢¥§¨©¨§§§§¦¦¤¢£¤¥¤¤¤ ™˜¡¤¦§©©¦¡ ž™––™ ¡¢£¢£¤¦¨§§§§¨¦¡œ™šž¡£§¨¨©©‘“‘ˆ€|zzwuuvxy{xsmjiiiiikjllijjjijljhggfeeecb]WMIMe{ˆ˜•““‘‘ŽŽ‘Ž‹’••–™šœž ¢¢¤¢¢¥¨ª­°²´·º¼¿ÁÂÃÅÆÈÉÌÐÐÑÑÏÎÌÍÎÍÌÊËÇ[DC@9I»ÊÅÅγc.4=@<9V£ÐÍÆÇÇÃÁÃU.123242024467:9797=¯ÀÁÉs.1-*%*71/.,)(#" HŸ™”’C(%#"!%$&($.”³°³´µ·¶¸¹º»»¹µ°«§¥£¢¡ž›š››œœœ›˜˜™ ¢¤§¨©©§§¦¤¤¥£¢££¢¢¢¢Ÿ›š—’™¡¤¥¦¥¢œ—‘‰€~€‡˜žŸ¢£¡¡¦©©¨¥ ›˜˜›¢¥¤¤¦¦§_ˆ‡~zyxurstwwxtlifeecccedbcc`b``ac``_^^\ZYXTPF@I_x„˜—”’‘”’Ž‹ŽŒ’’’”•™›ž ¢¡   ¡¤¦ª¬®²µ¶¶¹¾ÀÃÄÄÆÇÊÏÐÑÐÎÍÌÌÌÌÉÈʽL<@@8SÀÃÃÈ”D3::;:;t½ÎÇÆÅÅÄÁÀ½K034.B€yl\TJ:788669Z·½¾Åi10++&v “•“Œ…{tmffdx•“ŽŽ@(x†‚~vY3"$(*-&A«·¶·¸¸¹»»ºº¹¶±­©¨¥¤£¡ žœœœžžžœ›šœŸ¢£¦¨ª«ª¨¦¥¤£¢¡¡¤£¢¡ Ÿœš—”“𡣦¤Ÿ™‡ƒyuuu‡’™›œ ¤££¨«¬«¨£Ÿš–™Ÿ¡¡ £¤£¢4^†……‚}{zwtqpqsutmifa`^^][XY[WVTWWWXYXYWUSQOMID;34]w‹“——”’’‘ŽŒŽŒŠŒŒŒ‘’’•–—™Ÿ¡¡Ÿ ž ¡¡£§ª®±³µ¸»¼¿ÃÅÅÄÇÌÎÏÎÍÌËËÊÈÇÆÈ³@48:6U½Â¾s24<881DËÇÀÀÂÁÁÀÀÀ·B256/_ËÇÉÄÀ·¦—”˜©¸¾½¿Äb)-+++‘²©§§¥¢¡Ÿ¡¡žžœ˜“‹47 ¡¤§«®›Q&*,,.'{¾¹»º¸º¼»»¹·²°«¥¦¤¥£¡¡žžž ŸŸŸœœž ¤¥§©ª«ª¨§¦§¦£¡¡£  ¡ œœš–”•› ¥¦£Ÿ•Šytrtv}†˜œ¡¦©ª¬±²±®ª¦£Ÿ¢¥¤£¢¢¢¡ž@5`†ƒ|{vwurooqrtqifc`^\XVURQPMNOOPRRPPOLKID@7,!T}‚ˆ•–”“‘’’‘‰‡‡Š‘“”–˜—šœœžžžž ¤¨«°µ·º¼¾ÂÄÆÆÈÊÍÎÌËÊÈÇÅÃÂÂÄ©93540O¾°V-68760S¤ÆÁ¾½½¾¾¾¿Àó?3451jÈ¿ÀÂÃÃÄÅÌÎÉÆÃÁ¿À¿Â_,.,()’«¤¡Ÿž›š›˜˜˜—•“,= ¡£¥§³«P(..0,O¶»»»»½¼ºº¸µ²¯«¥¤££¡ ¡œžŸ  ŸœœœŸ¤¨¦¦§©ª©§¨¦¦¤¡  ¡ Ÿžœ›™˜–“”›¡¥£š“‡{sqnnpuz‡’›¡¥¨ª®°±¯®ª§¤Ÿ¡¢£¥§§¥¥£ŸD@<^ƒ~~{xwutppprrqoida][YVSPPMHFECDIHHGEB@:0!P|„‡Ž“•–•“”––”‘ŽŒŠ‡†ˆŽ‘“–––˜™™™˜™š››œž ¤¨«®´·¹¿ÃÅÅÆÈÌÌËËÆÃÀ½ºº¹¸»™-./.(LŽA&53350d²Ãº¼¼»»¼¼¼¾¾Á¬>4352nÆ¿ÀÀ¿ÀÀÃÄÃÂÀ¿À¿¿½½X++)&*‘¦¡Ÿœ›————˜š™”’†'F¥Ÿ£¥¨©«¸œ6,/12<¤À¼½¼½¾¾¼¹¶³®©¥£¢ ŸŸžžžžŸŸŸŸŸ›œ¢¨¨¨¨©ª¨¨§¦¦¥¢ Ÿ ¡Ÿœš—•–”’”šš‘ƒ{qnnlmmptx{ƒŽ˜ ¡£§ª««ª¥£¢ žœžŸ¢¥¤¤£¢¢DCA8f||{zutsqnnnmnnjf`][XVRQMKHD>;99<=:3+' Hy‚†Š“”””•””“‘ŽŽ‹‰ˆ‡ˆ‘’‘’”“’”––——˜™š›œž¤§ª®°¶¼¿ÀÂÆÈÌÌÉÆ¿¸²®®®¯°µŽ%)'()+."-.-,1l·º´¶¸¸¹»¼½½½½À§84493qĽ¿ÁÀ¿¾¿ÁÁÂÀ¿¿¾½»ºQ%'%#'ŠŸ›™šœ›–•—™˜˜•‘Ž€#M§¢§©«¬®²·Q*//34“Á¼¾¾¿¿À¾¹¶°ª¦¤£¢ žžŸ   Ÿž ¥¨§©ªªªª¨¥¥¤¢¢ŸŸ Ÿœš™˜•“‘”—“…}tllmjmmptsux‰“™™œ £¢¢›‘‘—œ›™š› ¢¢¢¢¡BCA;=jxwyvtrromllljjhb]ZXUSPMJGB;7542( D|ƒ„ˆ“•–—•’‘ŽŒŠ‹Œ‹ŠŠ‹ŽŽŒŽŽŽ’““““•—˜™˜œž¢¤§ª±¶¸»ÀÃÅÉÊÅ¿µ­¥¡¡¡£¦¯€!&%%%$%))++.y¹¸±²³¶¸¸¸»½»¹ºÀŸ53242tȾ»¼ºº»¾½¼¾¾¼»»º¸ºM%&#"%‡˜–•—–•––“’’”Œ‹Žw\ª¤©­®®²´»_(1263–ýÀÀ¿¼¹·´±¬§¤£¢¡¡ŸžŸ ¢¢¡ Ÿœœž ¤§¨§©«©©¨¦¤¤£¡žž Ÿœ›™˜—•“‘ƒurkijkloqqtstx}†Ž“˜™š˜ƒ||ˆ•˜––˜šž¡¡¢¡¡CCC@7?kuvxurqnlkkjjlhc^ZWTRPMIE@<851( =|‚……ˆŽ”•–•”‘Œ‹Š‡‰Ž‹‰‰ŠŠ‰‰ˆ‰Œ’’‘””“’–™œ £¨®±¶¹¾ÁÄý¶ª¡œ˜˜™›Ÿªp!!!!"$%&&%T¶¯­¯°²³µ···¸¶¶¶½‘/100.R˜¤®·º¼ÀÀ¿½¼»¼¹¸··¶H"$ #‰¢›œ™–’‘”•’‘‘ŠŠŒm e¯§ª¯°±¶¹¿Y/0588¤ÅÁÀ¾½·´²°«¦¡¡¡¢¡Ÿ  ¡¡¢£¢¡ žŸ¢¦§¨§©©§¦¤£££¢Ÿœž ž››š˜–”’މ„tolkiiklnqrsusx~ƒ‰ŒŽ’‘‹€tpqŽ““•–šœžžœ›œCBCA=8>outtrqmmlijhiheaZURRQMID>975(  8x}ƒ„†ŠŠ”•“‘ŽŠŠŠŠ‰ŒŒ‹‰‡†…„„ƒ†‰‹‹ŽŽŽŽ’–›¡¦©­±³¶¹´±© ›—•••˜›¤h#  ""$$3›®©­®¯¯¯¯¯°³´´²»„*--,,'(1>Lau…˜Ÿ¡ §¼¸´±E#!FTSYYZ`dagigkpŠ–c " p±«°³¶¶·¼¬A6346F·ÄÁ¾¼º¶´³®¨¢¡ŸŸŸžžŸ¡¡¢¢¡¢ Ÿ  ¤¤¦¦¥¦§§¦¥¢¢£¢¢ š››˜•”“‹€{vqnkihhkmnqrqtv{‚„‡‰Œ…wpor|‹’’•–™š›š–““AA@@>=5Bqrrtqnmkifffffc]WSROMHB=74* 0r|‚ƒ…ˆˆ’”ŽŒ‹‰‰……‰‹Š‹Š†ƒ€€€ƒ„…†ˆ‰‡ŠŠ‰ˆ‰ˆŠ–˜œŸ¢¦¨««©¥™–”‘”––—ž`5 !";ž§¤¦§¦¥§¨©©­®¬µt$,+)*+(('&$'/028:;5„Áµ±«? @“’“›_ "&"z´¯´·¸¸»Ãl59573tÄ¿¿½»¹¶µ²­¦¡Ÿ ž›œŸ   ¡¢¡ ¡¢¤¤¥¥¥¥¥¥¤¤¢¢¡  žš˜˜š˜“‘Ž‹|trqmlighmlnpqruwz}€ƒ‡ˆytomrx~ˆ‘”˜š›™—‘Šˆ@@@?<=;1Jpprqnmkifcacca`[UUPMFB<9+ 3m|…„†ˆ‰Š‹ŠŒ‹Š…‚„„ƒ‚€€}}}||~~€€‚ƒ€ƒ‚€ƒ‰Ž’–˜™ £¤¤¡œ—•““–––”šQeTM£››Ÿ¡¡¢¥¦¦§§§­e))(*+,+,./25446872‰À³­§>Dš˜š¡^##&$µ°µ¶¸½Å~/9875=®Á½½¼»¹·´°©¤ŸžžœœœœœŸŸŸ ¢¢¡¡£¤£¢¢¤¤¤£¢ žžœœœ™˜˜–—”‘ˆzupljifehkkmooqtwz}~€}yslknv|€†‹”˜››š“Šƒ???>=:75,Nnqppmjgedaa_]^[XQNHB@8$ -  )jx€‚„†ˆŒŒ‹ŒŒŒŒŠˆ„~~|xwz||}|{z{{{z|}~{z{z{|~€ƒˆ‘“–™Ÿ Ÿ™—“‘“–˜•–Io˜2j¡–šœ™ ¢¡£¢£­U%%'$)'%'+,.0214785޼°¨£8 GŸž¨\#&%(&‡Àº¿À¿ªj/7;:82ŠÇ½¾½»¹¶³®©¤Ÿœ›››››œ›œŸžž ¡¢£££¡ ¡¢¤¤£¡ Ÿœ›žŸš••••“‘ŽŽŽ…ytpmhfeeeilmnpswz~}}|zvvnikrx~~‚Š•›ž›–Ž…w?>>=<:85/)Kpllkihfcb_][ZXUOIE@. - %ct{ƒ…†‰‹‹Š‹‹‡…‚€|{zxuwxzywyxvwxwwxxuttuwx||{}€ƒ‡Œ“—›œ—–’Ž‹Œ”–‘Cq™~ x™’”–š››œžŸ¦F!"#)~ŠveTJB:965767’·©£›0K£ ¤¨V$&&)#^ˆƒzs[:+49=<1xƽ¼½»·³°¬¦¢Ÿœšš™š›š›œžŸžŸ¡ŸŸ¡¢ žž¡¢¡    ŸœŸžœš—‘‘“‘Ž‹ŒŠ‚vqmlifdcehkmnsy}yxvvtpghmrx||€Š•šœ•Š‚{w?==;;8641-*Gjkgffdcc_]YVRLLE, - - %_ryƒ„ˆˆŠ‹‰Šˆ‰ˆ„ƒ‚}{zyxvwxwvuvvttttttqorsstwwwww{~…‰•—›š—”ŒŒ’–‘:m‹‘g&„Œ’”•–˜˜™›œ7!! 2¦¸¹»º¸²«¦¢œ™––¨¬¡š+*UTTQONKIHIIKJrª¤¤§N$''))&))%(*/34:6:}Á¿¾¼¼¸³¯«¦¢ž›šš™™š™››œžžž  žžŸŸ ¢¢  Ÿžžœ›œ™–”’’‘ŒŒŠ‹‰spmkidcdfiilqz‚…ƒ}wuusrqkchoqv|€†”›ž—’†xw><;;:54411/):_gfb`ab_ZUMKI:!  - Wrw}‚……‚†‡„‡ˆˆˆ†„€|yzzvwvuutvtssrsrqompqptutwwvwy€‡ŒŽ–›™•“Ž‘Œ6n‡…‰J6Œ’“”—˜—‘.7Ÿ¦«²µºº¼¾½¿¿¾º±¥š“‚%C”‘‘”••—œ£¨§¥¤ªL$((*,-020234213PŸÊÀ¿¾½º³°¬¨¤ œš˜™˜™™™™šš›š›ššššœ›œž ¢¡¡ Ÿž››››š™–•“‘ŽŠ‹‹Š‰‡~vtpnhcbegklqw„ƒxvtqonlhdhnow‡’ ž™ˆƒ{ww><::85434311,1Oa^\\ZVPNL7 - - Lqx|‚ƒ‚‚‚„…ˆŠˆ‡‡‚{{ywwvtuutttrqsrrpooqprrqssttx|†‡–—–“‘‡‡†ˆ‰‰6o~|}‚4G‹ˆŠŽ‘’’”‡%:˜š¢§¬°²³³³´²°¬¥šŒw J‹ˆ‰Œ‘’–—™œ¡§¨ª©¬M&++-/-+.,-1/?b‘ÁÌÂÁ¿»¸±®¬¨¤¡žš˜—˜—–“’“””’‘••”’’–—–™¢¡Ÿš˜—˜˜–“““’‘‹ˆˆˆ‰ˆˆ„}|ysnidbdhmu{ƒˆ„€yuuspolhdefimw…“›¡¡Ÿ™”ƒwuw<;9877655331/.)?ORRQLIA"   - LVas°ÂÉÅÁÁ¿¹·´¯¬©¥ œš•“‘‘ŒŠˆˆŠ‰ŒŽŽ‘Ž‘•–˜˜••–“’‘”“Ž‘’Œˆ‡‡††‡ƒmtxslfcaensy†ˆ†{tpqonkecfghio‚“¡Ÿ›•ˆ‚|xwz;::877442320.--3?A@EG5 - *p{~ƒ„„„……†††††ˆˆ…~€€}{wvtqrrqrnqqqpnmnoppponpqruxz~†Ž’“’‹ˆ„‚„„/"uƒ‚~{€fp‡ƒ……ƒ‡t>‹ˆ‰‰‹ŽŽ‘’Šˆ„€€…a]‡†‹ŒŽ•–™Ÿ¥«­¯±³¶Q-16;<ŸÁÄÉÌÍÏËÅÃÿ¼¶²¯«¨¢™–’Ž‹Šˆ‡ˆ†…„ƒ„„ƒ‚‚…†„ƒƒ„†ŠŒ‹Š‹Œ‹‹‹‹‰†„„ƒƒ„‚aanmjeabhmu|‚„…‚{tqoomlhb`cgiiju„”š—”Žˆ„‚zwz€97788842310/,0;A@;8=4   - $c{€‚„„„…‡††‰‰†…‡…€€~}{xwurqsspnoooonnmnoooppqruuwz}‡Ž’’’Œ„})!wƒƒ€{Y(ƒ~€€~‚jDƒ~€ƒ‚ƒ‚‚€~}~‚Ueƒ†ŒŒ“–—ž¢¨­±³µ´µQ2369H½ÍÉÊÉÇÇÇÆÄÿº·²®ª¦¡œ—‘Š…„€€€}}|}}}|zzzz{z|}|ƒ‚ƒ…†ˆŠ‰ŒŠ‡…†Š‰‡ƒ‚‚€~€‚€bYdhecabgkxƒ„|upnnmmjd__cfhhjmr|‡‹‹ˆ‚€}ww‚5676665332/,1BGDA:90  - _}‚„……„‡ˆˆ‹ŒŒ‹‡†„‚€~}|{zwutttpopnoopqpprrsuvxwxy{{}‚‰’’‘‡v%&w€~~{y‚G=…~||z~_H…|}€}{||yxxz||}„Ks‹‹‘”˜›ž¥§«°´¸¹¸´J1344J¹ÆÇÈÉÈÈÈÆÃ¿»¶¯ª§¢œ˜”ˆ…ƒ~|zzzxwvxywvwvuuvtvyz{y{~~„…ˆ‰†ƒ‚ƒ‚~}}~~}`[`fccbcglw€ƒ‚|wsoonlke_^`ceggjkmsz‚…ƒzxz}‚‡578645542219EIGB>?:   - - Ly‚ƒƒ„„†…†‡‹‹‡……ƒ€~|{xvuusurqqqpqrqrtuy{~ƒƒƒ‚‚ƒ€„‹ŽŽŽŽ‰‚}{m )z}{{|}}~~4Mƒzzx}S.RV[bdhopsvv{~|€~„E"ƒ›—–›ž¢¤¨«­±´¹¸¹¹»´I5693M¿ÆÈÊÉÈÇÆÃ¿º´¯§ š–Šˆ…€}|zxwvwvttutvtrrsrsqppqsuvxyyz|~€‚‚‚~€~~}zzyz}{`[`baabehox€~|xsnmlkhb^]beggeffimx€€}yw|ƒŠ7865533414BJIGE@<@  - :x‚~}€€€ƒ„„……ƒƒ€}{zwxwuvuurrrqrru{€…‰Ž”“’“Ž‘‹‰‹‹„}yui"({zzy{|||v&^ƒxv{M#(+06:R~„=`twz†‰–˜š›žŸ±»¼À·H7776XÆÊËÉÈÆÃÀ½¸²­¦ —’Œ…‚|{yvuttrrrqsttrqqpqooooooqrsuvuvxyx||||||~~|{zvvvvz|x`[___`bcis|€ztnmjigc^\`dfeggbglp{}}{vyˆ•77534322>JPJIFD>2   - - .wƒ~zyz|}}€ƒ…„ƒ„†„ƒ}}}||yxwwvrrtvz†‘—𠢤¤¥¥¢žš—”Œˆ†ˆŠ…|wro_PJE>Gwxzy{yzxx|dl{rwF/€€5!$&)+.1147›ÂÀõF7887hÌÈÈÇÅý¹´±¬¥ž—”Žˆ€|ywwwtrsrsrqprrqponllmmopnopprsrrstuuwyxxxyyzxwvtuvuwzv]Z[]_`delu}‚‚|tojkhie]Z\bdgffhfms{~|{{vv|‡™Ÿ7553229JVTPJHE:/!  - *f€|xxwz||~}~‚‚„†„…††…ƒ‚ƒ€{zyzyvy|~†Ž– §ª¬¯³±²µ´±¬©¢™’‰„ƒ…†…€ytoqsx{{wvwx{{|}|{z€V#&pvyB5€0"""%'(*+,//15ŸÆÁÇ®B:;<7rÍÈÆÃ¿½·²¬¦Ÿ™”Œ†€}zwvutssrrqrrqqpomlkjiklmmnmmooppppprrsttvusstuvuuututuwoZYZ^aceinv}€|vqniigfa[[aeejhhimu|€|}|zvz†”œ¢¨52123ASWTSKE<70,! - - - "[y~zwtrswwyz|€€„††‡†ˆˆ……ƒ€{{{yz‚‰‘™¤­±³¸¹»»»º¹¶´±¨œ„€‚„ƒ‚|wtrqprtsstuyz|}~}}|sjbXOI;[ww@A‚~ƒƒ. ""#&')*-/2359©ÇÂÇŸ76530…ÌÅľ»¶±¬¥ž™Ž†‚|yvutttrrrqqqppoomkjihhhghijijlmmmnopoqrqppqssrsrttstrsssukVW]`efimsy€yrnlgghe\Y\aefjkjnt|„…~{zvx‚‘œ ¤§31/-3AA?A=75320/#  - - - - U{{wrrqrrstyz}€ƒ„…ˆ‰ˆŠŠ‰‡‰‡…‚~~‚…‹“𥭳º»¼¼½¾½º¸µ±ª£˜Š~{|~€€}zyyvuttuuuvz|||~}|}}}|x{}|{xxoaZSKD<62,)$#I‚€†) "!#%')*+-33/<­ÃÂÆ›S\bv‹µÇþº³®§¤ž•†~zvuttsrqppqppoommljhhghhfdghfggijllmmnnmnpqppqqrrpqrpqqrpqtqbadfhjntyƒ}wojigge^XY]cfjjlpsyƒ‰†}yut{‡“œ £¤1.,./.,.43334422' - - - - - Lyxwtqpqrssty|„‡‡‡ŠŠ‰††††‡‰†„‚ˆ‹’›£«²·¹»»»¼½¼¹¶²«¢—Œ~{zz}ƒ‚‚~{zzzzy{zz|{{€‚|zzz{}|z€{zyzywxxwspnlhcYTQl…‰ƒ?2.2300.1656;@FGNSZv¾ÂÅÆÃÂÊÏÒÐÉþ¹´®ª —ކ‚|xvsrrqopooopoollklljjijmlifiigeffiklllmmnlnpomnopomoqponnnprqighjiovƒƒxqlihge`YX]cfijjmsw~…†€zvqs‹”›¡££.,+-.-/043566753   - - - - - - - - Euttsrsssstux{‚†‡‡ˆˆ†‡ˆˆ…†ˆ‡………‹Ž— ©±¶¹»¼¼¾¾¼¹´°­¨š‚zxyz}…………‚€~€~ƒ‚„„„‡…‡†‚‚‚€ƒƒ„„€|{xyz||~}~€‚‚ƒˆŽŽ‹ŠŒŽ‘““•™ž¡¤§¦©¬±¹¼¾ÁÆÍÊÈÉÉËËÈÉÇž·²ª£›‘‰„~yvtsrponmmmnonoomklnnnoorvurpmjihhfegjjilloonoononnmmlopnmlmmqpkijkoxˆ†{tnieeb`XV[_dghkmqw€‡ˆ€zwurx‚Œ”™ £¦,+,./00224677742  - - - - - - - :wwvvuvttuvwxy~ƒ„ˆŠ†…‡‡ˆ‡‡‰Šˆ‰Š‹•œ¢¬²µ·º¹º»º¹³­¤˜‘yutux{}ƒƒ†‡……‡…ƒ„„„„„ˆ‹ŠŠ‹Š‹Œ‰‹‹‰‰‰‹‹ˆƒ~ƒ‡†‡‰ŠŒŒŠ‘“‘“—˜œŸ£¤§¬²¹º¹½ÁÁÃÆÅÇÊËÉÊÊÊÉÈÉÇž·°«¥šˆ‚~{xtqrrponnlmnnnmoomnopprsux{yxvrqonkigfgigiklnnonnolllkkklllkmnpoljkqz‚„‡…zslhfccaZTW^dfhikqv{„‰…zvssu}‰‘˜œ ¤¥+++-/00344455762   - - - - - - - 1s{}}}|zxvvwwvzƒ†‰‹ŠŠˆˆˆ‡ˆˆ‰‹‹‘•™ ¨¬±³¶¸¸¸¸µ°¨ž˜ˆ~usqqqty|€‚†ˆ‰‹ŒŒ‹ŒŒŒŽ’““”“‘‘’“•“’“’Љˆ‡‹ŽŽ’”•““•˜——™˜˜¡¤¦¨©­°°·»¼¾ÁÅÅÇÇÈÊËËÊÊÉÈÇÈÇžº´ª¢›’‰~zwutrqpqpnnnnonooopppqqsstwxz|{{zywvutppjhhhjklmmmlmnljkkjjjjkijnrplov{„‚|rjgedb_]USX^cegimsx€‡Šƒxurrx‚Œ“šž ¡¢)**,-./11214112*  - - - - - - - - +n€„‡‡…„€|zzzzy~…‰‹ŠŠŒŒ‰Š‹‹ŠŒŽ’—𣫭®°²µ··´«¡œ–‹ƒ}vurqqsuw|€ƒ‡‘””••••–˜š›œœž¡Ÿ››žš™™˜•’Ž‘”““•—–™šœŸŸœœŸ§«­¯±´¹¼½¿¿¿ÁÃÆÇÇÈÊËÊÊÊÊÈÈÈȼ¸´¬¡™‹‚~yywuspnonnnmnnnoprrrrsttuuxyz{}}}}}}{|{zxunkjkkmnkllmmlljlkjihhhikmqnov…‡…~yrleddb`\VTV\bdfilot|„Š…}wsot|‡•—œ¡&''(())(''''%$#   - - - - - - - - "k‹‘Œ†ƒ|~}}„‡Š‰‡ŒŽŒŽ‘•™¤ª¬­®¯±´²­£šˆ€|yustsqsx}…‹’—™¡£¤¤¢¡žžŸ¡¤¥¦¨©ª©¨§§§¦¥£¡¡Ÿš–•—ššœ››œœŸ ¡¢¥¨¦¥¥§­°³µ¸¹¼¿ÀÃÄÄÅÆÇÈÉÊÊÊËÊÉÈÈÇÇÇý¸°¦•Œƒ}zxvvspnmmmmlmnpqqstrrstvwwyz{||}}€€}||}}|{xqmlkmmklkllmlkjjjiighfghiojju€ƒ{qjd^``^_ZRVZadefkkox~„…}xvqqy€‹‘“–™œŸ!! # ! !!"!   - - - - - - - - ]˜›š˜•‘Œ‡„„‚~}‚…ˆ‰ˆ‹Ž‘’‘’”–™ ¦ªª¬¬­°±¬¢™…|{xusutsyˆ‘œ¦¬±¯°®°´µ·³¯«©ª¬¬­®±²¯¯®­¬¬ª¨¦¡œŸ¤¦¦¤¡ŸŸ¢¦¦§§©¬®°²³±´·¸»¾¾ÁÄÅÆÇÉÊËÌÌÍÌÌÌËÉÉÈÉÇÆÀ¹´®¦š’ˆ‚{yxvtrpnllkjlnppqrsuvuwvwxz{{{|}~}~~}~}}}||ytqnnljkljljkjjjjhigfggfgjjYU_ijhfg`a^\Z]]ZTRX\`ccdgmpw}€|xtrorz…Ž“•˜™žŸ"""" !!##"#"!    - - - - - - - - Nˆ—Ÿ¡Ÿžš—‘Љ†ƒ†ˆŠ‹ŒŽ‘’’“”–™œ¢§ªª©ª«­­§›’‰‚€~xwvyz~‡˜¥«£‡mWMJJP_œµÂ½³³³³³´µ³´µ´´±¯­«¥¤¦«®¯«¨¦¦§«­®°°²²µ¹¼¾¾¼½½¾ÁÂÅÅÇÈÊËÌÍÍÎÎÌÌÌÊÊÉÈÇþµ¯¦ š‘‹|xvutrqonlloqqssrqstvwz{{{|}~~}~~€~~}~}|||xurppnlllkkjjjihihffdeeeicMJKSSTWYXY[[[[ZUQTX]bdeehlqx~}xurnpv€‰–˜˜š›œž !!"""!"   - - - - - <€‘Ÿ¦¥¥£ž™”І„ƒ…Š‹ŽŽŒ’““–™š¢¥§¦¦§¨ª©¥™‹‡†‡†ƒ…ˆŠ‹‘š¨±–c<0034453048Gw¯Ä»¹ºººº¸¸¸¹º·µ³±°²¸º»¸³±¯¯²´³´·¸¸¹¼¼¼½¾ÀÁÀÁÂÄÆÆÇÉËÌÌÍÍÌÍÍÌËÊÇÇÅÁ¼³­§ ™Š{xursqpponokWZ]aitvuxxxz|~~}~~~€€‚€~~}|||zyxusronnlklljifihfbcbbcf[FIJJMPQRVY[ZZZVQRV[^_cfeimqy}ytrpou~…Š“™™—š›ž !!" - - - - - - - - - - )q”Ÿ¥©©©¥¡›–’‹Š†ƒˆ‹‘Ž’••—››ž¡¡¢¡ £¦§¨¥¡œšœ›š›Ÿ£¤§«·§f0(/7;889:9<::5.@{¼Ã»¼¼¼¼»¼½¼º·¶¶¸¼ÃÆÊÆÅþ¸¹ºº»½¼¼¾¿ÀÀÀÂÄÄÄÄÆÅÆÉÌËÌÍÌÌËÉÊÉÉÈÆÄÀ¼¹°©¢”‡ƒzwutqrqpppoy>j{xxz|{|~~~~‚ƒ„…„…„ƒ‚~}}}||||zywvtrollkkjhghgeaababgM=BFIKNOQVYXYXWQOQVZ^`bdgkotyyuopqot…Š’–•˜šŸ  - - - - - - - - - - - - `Š—¢©«ªª©¤žš–‘Ž‹ˆŠ’“’“‘“–—™šœŸžŸŸžŸ¢§¨ª®²·¶²²²´·¹¼ÄšB'1524:95457;96784.Q©ÉÁÂÀÁÂÁ¾»ºº»¾ÄÇÈÑ—q‚Ž™¶½»»¼¾¾¿ÁÂÃÃÃÄÆÆÆËÐÐÏÎÉÊÉËËÊÈÉÊÇÅÄÁ¾¹´­£§c&*),+Mxstrqppont[f|ywxz{|}~€€„†‰‹‹‹‹‰ˆ…„‚}{{||}~~{zzxusrqnllkjhgfccaaac=3œÉÂÃÃÃÃÂÄÇËÌÌÌÆV16697P»¿¾¼¾ÀÂÃÂÃÄÇÇÇÇÏo0240KÈËÉÉÈÆÄÃÁ¾¹¶°¨ š“‰//qrqqqqpoyHi€|||}~€‚‡Œ”••”“‹ˆ†„~€€€}~}zxxwvurrrpopmlhdd>.379>DFJLNQSSPLOSUZ^^]_dghnsspnoprv|‚‡ˆ‹ŒŽ‘”˜   - - - - - - - - - - - - -  `‘Ÿ¦¬°¯¯®«¨¥¤¡›˜™˜˜›žžœ››™—•’‘‘‘“˜¢°ºÁÅÆÈËÍÍÏz-58::0N•ÁËÊÈÈÉÊÌǯf59;;:.PÀÆÃÂÁÂÅÉÊÌËÄÇ{+633576œÅ¾¿¿ÂÃÄÃÆÅÇÇÉÊË\49:4eÍÇÇÇÅ¿¼¹¹¶­§ ™‹t!-oqqrrsqsck€€‚„†‹‘”˜™˜—•“‘Œ‰……„‚ƒ‚€€€€~|{{zyxuxxvwuturp]/+18;;ADGJMPRQMKQTZ]]^]`dginropootv{|€‚ƒ…„†ŠŒ’–   - - - - - - - - - - - - - - - R€Žœ¤¬²µµ´±­««©£Ÿœœœœœž   žžœ›•’ŽŽ•¨´»ÀÃÇÊÉÑŠ-4696/sÁÑÉÅÅÅÅÅÆÄÁÃÃ…79<1MœÃÄÃÁÂÅÊÌÎËÂÆ¢//10114.kÅ»½ÀÂÄÄÅÇÇÇÈÉÍÁH6885~ÌÄÅÄÀ½º·²­§Ÿ™”‡ƒ`,npqqrsrw1n‚‚ƒ„†ˆ‰Œ“–™™™˜—•’ŽŒ‰‡ˆˆ†††††‚‚‚‚€|~|}|{{{|}|{zyws3%+-3:;?BEHMQRPJJPUY]^_`acdinmlmoswz„…„‡†‰Š‹Œ“   - - - - - - - - - =yˆ˜¡ª±´¶·¶³®­¬«¦¢¡¢  ¡¢¡ Ÿž™“ŽŒŒ‰‹Œ”žª³¸¼¾Àͱ707<71ƒÎÎÇÈÈÈÆÆÅÆÆÅ¾ȉ32a¸ËÃÄÅÆÉËÌÌÉÀ¼¹H&0/0001/?³½¾ÁÂÄÆÆÇÇÇÇÇË«:9874‘ËÁÁ¾º¸³¯ª£œ˜‘Š‚B#kpprspyRtˆ†ˆˆ‰ŒŽ‘•˜™œ›™—•‘ŽŒ‰Š‹ŠŠ‹Š‰‡…„‚~~~~€€}||U#*,7;?ADFKPOKIJOUY\]_``ceiljklov~ƒˆŒŽ‹ŒŽŒŽ‘’  - - - - - - - - - - - - 'j‚Ž™¡¨­¯²´³°¯®®­ª¥£¥¥¥¥¤¢¡ žœ—“‹ˆ‡…†‡ŠŒ’©¯²´¶Äs+:<=2ƒÒÇÅÆÅÅÈÈÇÇÇÆÅÅÃÃÉ„ÆÌÅÆÇÇËÍÌÊÆ¾´¼n!--../1241†ÆÀÁÂÄÅÆÅÆÇÇÇÏ”4:889ÆÀ¾½¹´®©¢”Šƒ|zy.eqrrrshv‰‹‘‘’”˜›ŸŸžœš–’ŽŽ‘ŽŒŒ‹ˆ…„‚€€‚„‚€~|r# - !(,7AGJJJIHJMPUXYZ[\_abeknt~‡Œ’”“”—™˜———˜™˜˜    - - - - - -  - - - - - - - - - - - 0u›¢¦ª­°°²±¯­­©§¥¦§§¦¥¥¥£¢¡›“І„ƒƒ„ƒ„†Š“Ÿ¨°¹¡-+052ÌÄÆÈÈÈÉÈÇÆÆÅÄÅÅÅÅÅÇÆÆÇÇÊËËÌËÅÁº¸³E(0./+::279:9¦ÅÀÁÁÁ¿ÁÄÆÅÂÆa3979E¯¼¸²­§£œ’‹„ƒ~zxu|ADtsrxZ)…‘‘•––––˜™š››¢£¢ žœš™™š™™š™—•’‘ŽŠˆ‡„…ƒƒ„„†ˆ‡Šˆ‰ƒ|j "(+/9?CHIHFGHLNRUXY[\]^_ajr~…‹Ž‘”•———•”—›œžœ  - - - - - - - - - - - - - - - - e€Œ™¢¨«¬¯°³²°®­¬ª¨§§¦¥¤¤¥¥¤£¡˜ŽŠˆ……‚‚‚ƒ…Š“ž¨´‘%(),2¦ÄÁÄÆÆÇÇÈÇÆÅÄÃÃÄÅÆÆÈÇÇÉÉËËËÉÆÂ½¸¾s$/0/01˜x-;8;6oÇÀÂÁÂÂÂÅÆÄÃÃS4765F°¶±ª¤¡™•‚€|yxutn"$%prwo"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4BBCDEFFGGIMRRSVUVWX\`_cfhlnoonnsyxy{€… - - - - - -  - - - - - - - - - - - - - - - - - - -  fŠ˜ §«­®°±²°²´³²¯°°°°¯°¯¯­¬©§¢œ–‘Œ†~~~~‰\­¨­°¶º¾ÁÂÂÃÄÅÆÆÆÅÃÄÅÄÄÃÅÃÂÈ£00113+I½À½½½Ãm08552lÇÀÃÄÃÂÀ¾Å‡+,(%!A—Іƒ~|wwxvuutsv1f|}/yš–M\ž›žŸ ¢¤¦¦¨§¥¤£¢¢£¤£¢££¡¡   žœœœž¢¥¨®±³·¸¹ºº¹¶²¬_ - -  (07>AAEGDGHFGJMPPQSTUVXY[_acedgihkmquw~„’˜ - - - - -   - - - - - - - - - - - - - - - - - - - Nz„“ž¥¨¬¬¬¯±²²³µ´±²²±±°²²²¯®«¨¤ž˜“‡ƒ€€~~~!F¥¡§­²¶¹½ÁÃÃÄÅÄÄÃÅÂÂÄÃÃÄÅÄÈ¿N,300/7¤Å»»¼¹¾œ13223C¶ÄÃÅÃÁ¿¾Ãx%&" Eƒ|yxvuuvutttr)p‡hO£š™Hd¦ž¡¢£¤¥¦¦¦§¦¤¤¢¡¢¢¡¡¢¢  ŸžžžŸ¡¦ª¬°´¸º½½½¼»ºµ´‰#  - - &/8?BBFJFEJHGILOOOPQTVXXYZ\^^acdhjnr}ˆ“œ¢¥  -   - - - - - - -  - - - - - - It€Ž›¡¦©ª©¬¯±³µ¶´²±±°°±±°±²²¯­§¡œ•Іƒ~}~†Z]¥¡§¬¬¯´»¾¿À¿¾ÂŪÂÅÂÂÂÄÄÃÊl-630.,€Î¾ºº¹¸¹½M-3251ŽÊÂÅÃÀ¾¹½l!% NŽ~|xutuuuuttui"%z‡‰A&‘£ £F ! k§¢£¤¤¥¦¨¨§©¨¦¥¤¢£¢¡¡  ŸŸŸŸž ¥ª®²µº½¿ÁÂÁÀ¿¼¸µ§J  - - - - $-8?EGIKJFGIIIHLMLLPRSVXWWY]^`bfltŠ™¢£   - -    - - - - - - - - - - - - Tk|‰”£§ªª¬¯±´·¹¸¶µ³²²²°¯°°°¯®¬§¢Ÿ—‹†ƒ~}}€€„6b¤¨«¯´¸¸»»ÂÂ~?vËÁÁÂÃÀÌ19632/.[…Ÿ²¼¿ÁÂÌ€,5460^È¿»¶ºa !V…}{xvuuuuvvuwc*„’œ}"d®££¨M!!"lª£¤¤¥¦¨ªªª«¬¨¦¦¤¤¢¡¢¡Ÿ  Ÿ ¡£¨­²µ¹¾ÀÂÂÃÂÂÁ¿»³®k - - - - - -,7HGFHJKIGIIIGIIIJLOOTUUVY^`cnt„—›œš™•“’“  -  -    - - - - - - - - - - - - - - agm€Œ—¢¨ª«®±´µµ¹¹¶¶´³³³²³³±±±°®¬§£ž•ŽŠ…‚}~€y$G•¦§­®®®³ºÁ®_/83ɾÁÁű?56651--&$,>Qit{…m364459¬ÄÀ¾º´®²S_}{yyzyvwvvxzy|[/›žŸ§V  1¢®ª©«U!$o®¤¥¥¦¦¨ª«ª¬«ª¨¦¦¤¡¡¢¢ ŸŸ ¡¤ª±µ¹½ÀÂÃÅÄÃÃÂÁ¾º°‹* - - - -/HKFHJJKKKIKIGHJKNPSSVZ_cglsŠ‘‰‰Š‰‹‹  - -    - - - - - - - - - - - - $hfg{‹”Ÿ§«¬®±µ¶···¶³²³²²²²³´´´³±°¬¨¤–‹†ƒƒk.g‘§­®°²l9'5997’ÈÁÄÄb287670+*+)*-*+-.0076446,tº¸¶°§§Bk{wwyyyyzzz{}†W0˜¤¥¥«ž2#$s¶­®«¯] #$k®¨©¨§§¨ª¬¬ª©©¨¦¥¢Ÿ ¡¡  ¡¢¦«±¶»¿ÀÂÄÅÆÄÃÂÁÁ»²£F - - - - - - 3HFIJJKNQQNPRPRTWX^`adjnruz|~|~‚ƒ…††     - - - - - - - - - - - - - - 'kda{•ž¦ª®°²³µ·¸¸·¶µ³³´´³³µ´³³³¯¬ª¦¢ž˜‘‹ˆ…€€}{ƒc'E\a[C+ )12482O»½Ä‡3989:3.%#&'+.232367754351D·ºµ²§ š7$xyxxzzz|~~€‚†Š“` 1¨«­«¸v'$=¬²±°¬¯`"%$b±ªªªª©©««¬«ª©§¥£¡Ÿ  ¢¢¤¦©­±µº¿ÂÂÃÄÅÅÃÃÁÀ½¶¯m  - - - -6@DCCGMQWWWYYXX\_adceikmoqrtttvx|}€…Š - -    - - - - - - - - - - - - - - - ,nf_o‡’›¦®±³´³³¶¸¹¸·¶µµµµµ´µ³³³²¯­«©¦¤Ÿš“Ž‹†‚€{z€`!&(,03.<¿À­=79:=6Kš‹`='$(./1466242256*ˆ½±ª¡™’,3yvxy{~ƒ†‡ŠŽ™k"#%3«­°²´³J%$u¹²²±®³l$(%\±¬««ªª©ªª©«ª§¥¤ Ÿ  ££¢¦«¯²·»¿ÂÃÅÆÅÄÃÃÂÁ¿¹´’( - - - -  -4;;:>EGMSTWTUTVXZ]`cegfijklqswwy|†Œ  - - -  - - - - -  - - - - - 4rj]cy‹—£ª¯±²³´´·¹¸¸···¶µ´³³´³³´³±¯¬©§¢ž˜•‘‡…‚}||‚s0 #%)+&J¤ÄºÁ_.;;<:7ż»®”vVGC?<>BD91541)I¯¦Ÿ˜‘"E}wz{|€„…ˆŠ‘–œo#%%(/–¯¯°²²¹•-<«²²³±¯µz(*)#P±®®­««©©©¨§¨¦¤¢žŸ¡¢¤¦§«®³·½ÀÂÅÆÇÅÄÄÄÄÃÀ¼·¦L  - - - - - - - - -#5435;=AEJNPQSQSVWZ^_cdddinsuw{†‡Š‘  - - - - - - - - - - - - - - - - - 9qj^`mƒ“ ¦«®¯±³µ¸¹º¹¸¸¸¸¸µ´³´³µ¶´´±¯­«§¢žš—”Žˆ…~}‚}L"#&f²¿·À-68<<5}ô³³´¹¾»º·´²³¸À–151-,$‡¤—Œy%5/-/...--..(o ¦{!''((±°°³´³»n{º³´³°¯²*(("A¯±±±®­«ªª¨¨©§¥  ¢¢¤¤§¬®²·¼ÀÄÅÆÇÆÅÃÄÄÄÂÀ»²u - - - - - - - - -%0147:;=BHLOSSQRUW[\_cfhlmtx|€„‡’–  - -  - - - - - - - - - - - - Csk^]f|‘𣍫®°³¶¸¸ºº···¹¹¸¶µµµµ¶·µ³²°®­©£Ÿ™”ŽŠ‡‚~ƒuF<е²±¸«>388;4ZÁ¹²²²²³²¶¾ÃÇÉÊÇÆÂN*-*(LŸŠˆp hª¨ˆ*++,*†µ²²´µ´µµµµ¶µ²±°³˜0))$:©´³²±¯¬¬ª©©©¦¢¡£¤¤¥¦©¯´·¼¿ÃÅÆÆÆÆÆÄÄÄÂÀ¼¹@  - - - - - - - - - - - - ,2269:<@DIMPSTRVW[^bfimqvyƒ…ŠŽ’•“•  - -   - - - - - - - - - -  - - - - - - - Gvm_]aoˆ•ž£§¬®±´··¸¸¶µµ¶·¶µ³´¶´µµ´´´³±¯­ª¦£ž›—“މ†…‚‚…‡‰xP/ ,Gz¡«ª«®´x4;864<¨À³±®¬­­°¶¿ÅÅÆÅÃÀÄ‚#)($ %‡……b #j¬¬‘1.-.+~º³µ¶¶¶µ¸º¸¸µ²±°´¢6)*)5Ÿ´²³²±¯­¬¬«¨¤£¤¤¦¦¦©®µº¾ÀÂÅÇÆÆÆÆÅÄÄÄÁ¾¶©w* - - - - - - - - +/48;>BBFLPRUWWY[`dhmty~‡ŒŒ‘†t  - - -   - - - - - - - - - Rwp`\af{™ ¤ª­®³¶··¶·¸µµ¶¶µ´´´´³²³³³²°®­¬©§¥¡ž›—”‘Ї‡ˆ‡ŒŠ‚ztt}£££§ª®²§¤¦ š–ž¾¸µ¬©©©«³¾ÅÈÇÆÄ¿¼¹§/$$ X…‡T $d­®™5,-,-s¸³µ¶¶··¸¹¹·´²±±³¨<(++.“µ²²°°°¯¯®¬§¥¤¤¥¦§©¬´º¾ÂÅÇÈÉÉÇÅÆÅÅÄþº¶‰c' - - - - - - - - - - - - 6:ƒ‚{l_cccedgz‰”Ÿ¨¯²´¶¸º»¼½½¼¼¼¼¼»¹¹·µ¶µ³±±±²²³²²²°¯®®¬­­­«©§¦£¡Ÿš—‘Їˆ†ˆˆ‰Œ•¢§¬³·»¾¿ÂÆÆÄ½»¸±°­ª­ª¨©ª­¯°²¶¸¸º»º¹º»¼¹¸··»½¼¼¼»»º·¶µµµ¶³´´µ´²±¯®®®­­­­°±°°²²´³³µ¸»ÁÃÆÉËÌÎÌËÌÊËËËËÊÈÆÃÀ»·³°­©£¢˜nadgioo` - - - - - - -     $&&$&(&)*(’’‘‘’ŒG  - - - - - - - - - - - - - - - - A‰†~o`bcgihgo‚™¤ª²´µ·¹¹»½¾½½½¾½¼¼»¹···µ³²²±°¯¯¯±°°°±±±²±°¬ª©¦£¢ŸŸ˜•’Ž‹‡†…‡‡ŠŽ“˜¦¯¶¹½ÀÄÄÆÈÈÅÄÁÀ¼º¹¹·¹ºººº»¼¾ÀÃÁÀÀ¾¿¿¾¼ºº»½¾½¼½¼¹¹¶µ´´µµ³´´´´²±°°°±°±±±²³³µ¶¸¸¸¹¼¾ÃÇÉËÌÌÌÌËÊËËËÊÊÈÇÄÁ»¸µ²®¬ª¨¡ž€cbehlrmb" - - - - - -  -  -  -  #&$$'&&())‘Ž‘ŽŽŽŒ‰J   - - - - - - - - - - - - - - - - KŒ†ƒ~saachkkkkv…’𤭱´µ·¸»¾¾½¼¾¾¾½¼¹····¶µ³²²±±¯­®°²²±²³²²³±¯¬ª©¨¦¥¡ž™”Œ‰ˆˆ‰‰Š“œ£«±¸»ÀÇÄÄÅÆÆÆÇÅÃÂÃÄÅÆÅÄÄÅÆÅÆÆÅÄÃÃÃÃÁ¿¾½¾¾¾¼¼¾¼»º¸··¶¶µµ¶´µ¶³°°±²²²µµ¶¶·¸¹º½ÀÂÃÅÇÊËËÌÌËÊÊÊÊËÊÉÉÆÅÿ¼¸´±­ª©¨¤™Šjcfikmund# - - - - - -  - - - - - - - -  "#%$%'(''()ŽŒŒ‹ŠŠ‹J   - - - - - - - - - - - - - T‹‡†‚va^chjlmklx‡•Ÿ«°°²µ·¹½¾¼½¾¾½½»¸¶¶¶¶¶´³³³³³²±²²²²±°±±²´³³²°¯®­¬ª¦¢™•Љ‹Œ‘–ž£­¤OHuÆÁÂÃÄÅÅÆÎÑÐÑÊÊÉÈÈËÒÑÏÈÅÉÎÌÍÎËÉÊËÆÀ¿¾¾¿¾¾½»¹¹¸·¶¶·¶·µ³±±³³´¶¹º¹º¼½¿ÃÅÈÊËÍÍÍÌÊËËÊÉÊÊÊÉÉÇÄ¿º¶³¯¬ª¨§¥¢œŽthfiikntmf$ - - - - - - - - - !!"$$$'&&()(ŽŽ‹Š‹Šˆ‡‡‰D  - - - - - - - - - - - - ^Љ„|b`cfklllgo“§®±´¶¸¹»¾¿¾¼½¼¼»¹¸·µµ¶µ¶´´µ´´µ³´µ³´´´µµ´µµµµ¶´±°¯¯¬ª¤ œ˜—–‘ŽŽ”šŸ¯e ,,žÅ¾ÂÄÄÄÄ~hl˜ÑÊÉÇɽr’ÊÉ­jhfdbgrŒ«ÇÉÂÁÁÀ¿¿½»¼»º¹¹¹¸·¶µ´´¶·¸¹¼¿ÁÂÄÅÇÉÊÌÍÍÌËËÊÊÊËÊÉÉÈÇÅ¿½¹·´¯­ª§¦§§¥Ÿ–‚qiikjkqtlh+ - - - - - - -  -    "#$%%$#'&&‹Ž‹ŒŠ‡†ˆ†…ˆE  - - - - - - - d•Ž‹‡jbeghllmjjx‹™¢©°µ¶·º»½¾¾»»½¾¼¹¹¸·¸¶µ¶·¸¸·µ···¹¹¹¹¹¸¹¸·¸¸¸···¶µ³³±¯ª§¢žš˜•‘‘‘’”œ–*%,)\Ä¿ÀÄÃÃÇ\)//}ÐÈÅɼE.MÃÌ«312:>;5.6^¡ÍÇÄÅÄÄÄÃÃÁÀ¾¼¼»ºº¹»»¼¾½ÀÄÅÇËÌËËÍÍÌÍËÊÉÉÈÈÉÉÉÊÈÆÂ¿»¸µ³±®«ª©¨¨§¥ ›xmjkkknuvml/ - - - - - - - - - - - -  - - -  !"#$$$%&&&(ˆ‰ŠŠ‰‡…‡‡„ƒC  - - - - - - - - - - - - k•Žˆodfgikklkjp} §¯´¶¸º¼½¾½»¼½½»»º¹¸¸·µµ¸¹¹¹¸¹º»¼½½¼½¼½¼»»»»º»¼»¹·¶µ´°®«¦¡ž™••–•‘œa%$**•Áº¾¾¿Çy076,nÉÈÃÇ]6?±È»H6G«º·³–b-3vÊÉÆÇÈÇÇÇÆÆÆÅÃÁÂÂÁÁÂÄÅÅÈÊÊËÍÍËÊÌËÊËÊÉÈÈÇÇÆÆÇÆÄÀ»·³°­ª©©§¨§¨¨¤¡œ“‚qlilklqwxnk1 - - - - - - - - - - - -  -  -"#$#$%&&((‰‰‰Šˆ†„…„ƒ„? - - - - - - - - o–‘މ‚qcceggijjiiuˆ˜£­²µ¸¼¾À¿À¿¿¿¾¼»»º¸¶´³µ¸ºº¹»½¿¿¾¿ÁÁÁÀ¿¾¿¿¿ÀÀ¿¿½»º¸¸¸¶³²¯ª¦£ž›š˜–‘/!&!L´²¶¸»Ä2622,kÌÄÉn36›ÆÅ[1A¹ËÈÉÍÎ’8+eÉÉÇÉÈÉÉÈÉÉÉÈÇÈÉÈÇÈÉÊÊÊÊÌÌÌÌÌÊÊÊÊÊÉÈÉÇÆÅÃÂÁ¿»·±®ª¥¥¥¦¢¢¤¥¨¨¥Ÿš‘{mllnlnt{|pp5 - - - - - - - - - - - - - - -  #$##$%$'('ˆˆ‡‡‡„ƒ„ƒ‚B  - - - - - u—“‘Š„vbccddfggigk~’Ÿ¨¯´º½¾¿¿ÀÁÂÁÀ¾¿½½º·´´¶¹»¼ºº¼¾¿¿ÀÁÁÀÀ¿¿¿¿ÁÂÁÀ¿¿¼½¼»¼¼º¹¶²¬¨£ ž˜œj.r!†¯¬²³¾-1RR10lÈˇ23ˆÇÈo25¢ËÅÅÄÅÐ6.ƒÏÆÇÉÉÉÉÉÊËÊÉÉÊÈÇÉÊËÊÉÊËËËÊËÉÉÊÊÉÉÇÆÂÀ½»»ºµ±­©¦¥¢Ÿ   ¢¤¦¨§¦Ÿ•ˆsonnnlou}|ot= - - - - - - - - - - - - -  -  !!"#&%%$&‡††…„‚ƒ€}}: - - - - - - - !™”‘‹†{ecc`adffeeiu‰™£«±¸»¼¿ÀÀÂÄÄÃÁ¿¾¼¸··¹¼¼¼½¼ºº½½¾¿¿ÀÁÁÁÁÁÀÁÂÁÀÀÁ¿¿¾½½½½»¸µ³®¨¤ ™—7XžEA§£§¬´)+^¬<4-xÏ–32qÉɆ03‡ÎÅÇÆÄÃÅY1E¼ÉÇÈÉÈÈÈÉÈÈÇÈÈÈÇÇÈÈÉÈÈÊÊÊÉÉÉÇÇÆÆÅ¿»¸·´²³­¦£ ŸŸŸœ››ž¡¤§§¦¥›“}npoonmpy€{qx? - - - - - - - - - - - - - - - -   "#$%%%&&……†ƒ~~|}4  - - - - - - - - %…™–‹…{hcc_`ccdeegk|ž¦¬²¶º¾¿ÁÃÃÃÃÁÀ½¹¶¶¸»¼½¿¾»»¼¼»¾¿ÀÂÂÂÂÁ¿ÀÁÂÂÂÁÁÂÁÀ¾½½¼»¶´³¯ª¥št!‚w}¢¤¬Š((RÑ/4,ª72_ÇÆœ56iÍÄÅÃÂÀÈs44™ÌÆÇÈÈÇÇÇÇÇÆÆÇÇÆÆÆÇÇÇÇÆÈÈÆÇÇÄÂÁÀ½¸µµ²²°­«¥Ÿœœ™š™™šœŸ£¦¦¥ ˜soqrrpnr…}szE - - - - - - - - - -   "$%$$$$$…„„‚ƒ€~}z|8   - - - &†–”†|hcba`abcdfgem|–¢§¯³·¹¼¾Á¿¾¾¾½»¹¶¶¸¹·ºº¹¹ºººº»½¾¾¾ÀÁÁÁÀÀÂÁÀ¿¿¿¿¿½»½»º¶³±­¬¥EO”ŠŽE>˜•š ƒ%#I±¶r'-1|;2N¼À¨?7PÀÁÀ¿ÁÃÇb21”ÉÄÅÆÆÅÅÅÆÆÆÅÆÆÅÄÅÅÅÄÄÄÃÃþ¾º·´²®®«©¦§¤œ™–•”’’‘”—šŸ£¦¥¢š“€lpssrppv‚ˆ|v|P - - - - - - - - - - - - - - - - -!#$$$%&%‚„ƒ€~~|}|{yz= - - - - - - - 'ŠŸ›–މ‚kcbb`_``cefedn†›¥¬²µ¹»½¾¼º¸¸¶¶··¶´³³´µ²´µ´µ¶·ºº¹ºº»¾¾½½¼½»¹¸·µµ²°®­¬«©§¦¤žž‡azvtYlЇv; Ÿ¦B %%*,<¯»±A2=´¾½½ÀÊŒ67>¬Á¾¾¾¾À¾¾ÀÀÀÀÁÀ¿¿¿À¿¾¾¾½½¼½¼¸µ²¯¬¨¥¡ Ÿœš•‹Šˆˆ‰Š‘•›Ÿ¢¤ œ“…roqsrqpr|†Š}z€Z - - - - - - - - - - - - - - - - -  !!"%%&'%‚„€~|{zz|{xz? - - - - - - -  +ŽŸš–’‹ofdb``abbdefghwŒŸ§­±¶»»»¼¹¶µ³³µ·¶³±­®­­­­­­¯±²³³¶¶¶·¸¸·³°°°¯«©¨¤¢¡žœ››˜–Žˆ‘D*|y~m-“’Ÿˆ# ##%1¥´±E-.¢¾¸»²0-&c³¯³±±³³³´³²±²³³²²²³³³²²°°¯­©¤¡Ÿžœœ˜—•’Š…ƒ~€ƒ†‰”™Ÿ¤¦¦£™Švmortrqqu€‹‹}~…d - - - - - - - -  - -  "%%%&'€}}|z{{zwv< - - - - - - - - 2‘𗓆thfdba`abdefhej“¢¨¯³¸¹¹¹¸¸¸¶¶¸¸¸´±®¨§¥¤£¥§©§©««««©«««§¢¢¡Ÿžœš™•’Š‹Šˆ……~pKyte*„ƒ„’c#Š¡ =^sfT80„–‘“—™››œœž £¢¡£¢£¥¤¤£¡ Ÿžš——”‹ˆ…„{{}€‚„‹•šŸ§««¨¢”‚qnprsrqsx„ŽŠ{€†g - - - - -  - -   #$$&&'}}~€|{z{xywuC - - - - - -  2’ž›˜’ˆ{jjhda__abfehhfr‰¦°´·¸¹¹º»½¼¼½½¾»¶²ª¤¡žœžœ›ž ¢¡ Ÿ  ž—ŒŠ‹†‡„ƒ{|{vxzussrxB$T[]\]^^L!Zja nsrsx?]rx1">gonsz{ƒ††‹‹Ž‘‘“”—™›žœœœš—”‘Ž‹ˆ…~}{|z{€ƒ‡”—ž¢¦­®¬¦šŠtmorssrru|‡”‹}ƒ…d - - - - -  - - !###'('}}~}|zxwuusuE - - - - 2”Ÿš“‰{mllgcb`abachiho|‘¡¬²¶¸»¼½¿¿¾ÀÁÀÀ¿º´°©¢›š˜™š››››˜–“‹{yyywvtttrrqoolkje<59[kiiffffj]EC@\e@9:_iihhkI+*)Vfg?,1449AIS_hegjmqru{|y{z|€€„‰“—šœš˜–‘ŽŠ†„~{yz|}~„Š˜›Ÿ¤©¬¯®©¡“|mnpttssrv€Ž•‡€‡Œh - - - - - - -  - - - !##$')){|}|{yxturquP - - - - - - - 7— ž›–Šqqnleb_abcceikio‡œ§®´¶º½¿ÀÀÁÃľºµ±«¦¥¡ž›˜˜™š˜™˜”‘Œ„zvtutsrqrqnmmkhgea^]``^]^[^]^``aaa`^^_`cadeddcfcbb```_^`baaaababdfiknoqtutuuwzzz„Š‘•šœœœš—”‘ŽŠ‡„€~|zz|~‡Ž’™ž¡¦ª«°°­¦›smpqtuussy‡””„‚Šo - - - - - - - -  -  #$$&(+|zy{ywttssorR -  - - - - - - - - - - 9›¡ ˜’‹ƒvtumhea`bdcehjhiyœ§¯³·½¿ÀÀÃÄÃÂÿ½¹µ¯­¬¨¡ž›š›œ›™—“Œ„}wrrsqoponjijffggecdddefecddefgeddcbdcddcdedccabb````^^]^`^^_aaddeilmoopqqruwy{}€ƒ…ˆ–ššœš—–‘ŽŠ‰ˆ†‚~{|}}„ˆ‘”™ž£¦«­¯²¯­£”~lnpsstuuv–”„…Ž’u  - -   -!#%%'+{{z}vvtssrpmI  - - - - - - >¢¡˜“Œ†zuwrnhdabdcehiihmƒ”œ¦®µ»¾ÁÂÄÅÄÂÂÀÀ¿¾¹³¯¬§£¡Ÿž›™˜–’І€{wvrppommlnmmmoononnmmopmlmnmjjjhfghhhhghifefeddedbab```a`bccdfdejnpomoprsw{}~€ƒ‡Š”›žœ˜•“‹ˆ†…ƒ~}|~‚‰‘—œ ¥ª«®¯³²°¬žŒomprtutuvyƒ’™•‡’–x - - - - - - - - - -   -0!$%&&+yyzyuvssqpnnP  - - - - - BŸ¡¢ž™”‰}u{yrmgcbcdegjjlku…–¢©±·»ÀÂÄÂÂÁÀÀÁÂÁ»·´°¬¨¥££¡šš–’Œˆƒ|zwttrtvwxwwvvwvvuvtrqsrqpqqpponmmmnlmkjjikihghhgfddcccdcdeefghjmoponqsw{~‚ƒ†‹ŽŽ•š¡¡žœ˜”‰‡…‚€|}}„„Š”™ž£§ª¯±°±´²®¥–zkoqstuuuv~Š—œ”‰•˜y - - - - - - - -  * "#&&)+wxwvttrrollkW - - - -  J¡¢£ž›•މ€uz{uqleabcdfijkkm{™£¬³¸¼¿¿ÀÀÁÀÀÃý¹·³°«©¥¢ Ÿž›—”“ŒˆŠ‡‡„€€€„‡ˆ……‚‚‚€~|{yzxwxwuuttttrrrssrqnmmonmlllkihhhhiiikikmnpqsusrvz‚ˆ‰‘““•˜š›Ÿ¢¢ ™–‘ŽŠˆ†ƒ~|€‚†‰Ž–œŸ£¨«®±´´´´²«‰qmosutuvvz‚‘›Š“—šz - - - - - & "#&()+-ywttrrronlkhb - - -  N¢¢£Ÿ›•‹„vw|yuqkcbccegikkkr‘§®´¹»¾ÀÀÁÂÂÃÄÄ¿½º¶²®¬§¤¡ œ˜–”“‘“Ž“šŸœœ™˜™—•‘‹‹Š…„‚~}}|zzz{|zwvuvuvvuqsqqppnpqosttwy{ƒƒ„†ˆ–———˜›œžžŸ¢£¢Ÿœ˜“Žˆ…„ƒ€}{~‚†Š•›¡¦©«¯²µ¶··³­£‘tnoptvvwxw|ˆ˜œ‹Œ–˜{ - - - - - - - - - -  !' #&*)*+/ttrsrpmllkjfd - - -   P¦£¥¡›•ˆxx~|ysngabbdghjlmox‹— ª¯µ¹»½ÀÂÃÃÄÅÅÁÀ¼¹¶±®ª¦ œ™–””“‘”••˜™£ª±´´²±­®ªª§¥¥¦¤¦¥¤¡ž›œ™˜•””’‘“‘Žˆ†…ƒ‚€€€~~€‚ƒ‚‚„††‡ŠŽ‘™Ÿ¡¡ ¢£¤¤¥¦£¢£¥¤ œ—“Žˆ„~~|}ƒ‡Œ‘—œ ¦©¬¯³¶¹¸¹¸±§œ„lnqswvwyzzƒ›Ÿš‡™›› - - - - - - -  #'#&*,-.13trsqonljjihec - - - -   N§¦¨¥ž˜“‰}v|~zwqkdaccdgjkkmr}™ ª²·º½ÁÄÃÄÆÇÅÃÃÀ¾»·²®¨¢™–“’“‘’“’—ž¤©±·»»»»¸³±±´µµ¶··¸·´²±°°®¬¬ª§¥¦§¦¥¢ž›š™—•”””•–“’’–—™šš™™š›œ›› ¢¢¥©««ªª«­­¬­«©¨¦¦¡™‘‰„||}~ƒ†‹’˜£§ª­±µ¸¹¹··³«ŸŠtmoquwuvyz‰•ž¡–„‘™›  - - - -$&!'*-.035sqrpmlkhhgddd - - - - - -  M¨¦¨¥ œ–‘‹€x|~~{umhbcbefijjlpvƒ˜¢¬´º½ÀÃÄÅÇÈÅÄÄÄ¿º¸µ¯¨¢˜”“‘“•žª±·¼¿À¼»¸¶º¼¾ÀÁ¿ÁÃÄÃÃÃÃÀ¾¾¼½¼º¶¶¸·¶¶²±®­«ª©§¦§©¨¦¦§¨©­¯±³¯°°¯¬¬­¯±²³³²±²²´´³²¯¬¨¨¥ –Š„€~~|}~ƒ†‹“™¤©¬®²¶¹ººº¶²­£–ynopsvwwwy|‚œ¢¢‘†•œœž - - - - - - - - -  &&"$+/0246pnqmmkffffcac( - - - - - - - -   M§¦§¤ ž™•…y|~€{tneabdfgjjkou{‡”ž¦®¶»½ÂÆÆÇÇÄÄÇÆÅÃÀ½»µ°¬¦¢›™•’“™¡¨±º¿À½»»¾±±ª§§¥¥ž—˜—–”—¥·ÂÄÀ½¾¿¿¿¿¼»¼¼»º¸··¸º¸·¶¹¹½¸ª˜‡„Œ•£´¿½»»»»½½ºº»»»¹¸·³°®¬¥œ‘Š…€~}„…‰”˜Ÿ¤¨­´¶·»¼¼»¹´°§œ†nmpsvywvy|‰– §£‹†–Ÿ ¡~ - - - - - - - - -  -)(#!#(/4567mmlhkjgeddcb`3  - - - - - -    S¤¨©¦¡™–’Šzy€ƒ€}xslb`bdfgjlnqv~Š“¥®¶»¿ÅÆÇÆÅÆÈÈÈÇÅÁÂÀ¼¸³¯«¨¦£¥ª²¹¼ÁÀ¿½»»¾¼RA=;;<;98<7476=K\¯ÆÄÀÀÀÀ¾¼½¾¾¾¾¾½½½¼»ÀÁ¨~X?320,.39KpžÂÇÀÀÂÃÄÃÂÁ¿½¼º¸¶´¯¥™„~}€†‹”™ ¦ª®³¸º¼¾¾½»¹²¬ tmmpuxywv{„›¤§¡ˆ‹™¡¡£z - - - - - -  -((&!#*46889jliihhfccc`\[9 - - -   W¨ªªª¥Ÿ›™•‹~w€ƒ€{tpgba`cgijnoru}ˆ“𤝵¼ÁÃÆÆÅÆÈÉÉÈÇÇÈÇÄ¿»¹¶µ´µ¶ºÀ¿¾¼º¹¹¹»±?9:99::8;:6779;;5.@z»Æ¿½¾½¼½¾½¾¿ÀÀ¾½¿Á¢f:+01/430111,*7VŒ½ÆÀÁÁÀÀ¾½¼»¸·µ²ªœˆ€ƒŠ‘—›£§­±¶º½¿ÀÀÀ¾º¶­¡–}lnptwzxxyƒ‰–¡©©›ƒœ¡¡¢w - - - - - - -  -%)'"!!%17::aa`_]^]WVVSRPJ - - - -  cª©¬¬ª§¢Ÿš“ˆ}}„………zunfa^^aejlloru{„–Ÿª·¼ÀÁÂÆÉÊËËÌÎÍÌËÊÊÊÉÉÇÅÃÀ¿¾½½¼¹¶¶µµ•+)))$PŒŒ‘‘„pJ.(10/311’ø¹º»»¼»¹ÂŸ?0111/00),044.++2530...(k¸±®ª§£ š•‹ˆƒ€€‚…‰”šž£©¯´¹½¾¿¿¿¾½º¶±ª‹{opqtw{{{z}„‰œ¦®­¨‰‡š¤¦¥¥j  - - - 3M3$$"%%%,38:___\[XXSSUQPLE - - -  c¬©­®¬¨£Ÿ›–Œ~ƒ‡‡†‚}xslea_``ehjloqv{…‹•Ÿ®·½ÀÄÈÊÊËËÍÎÍÌÌÌËÌËÈÇÈÆÃÀ¿¾¾¹·µ±¯®'&$%q³®±²µ²²´²“K&-,..1;§»µ¹¹¹»ºÂ¥=23/11,);]™ ¡šŒmG--0,,.,"q¯£¢ œ˜”ˆ„‚€‚…ˆŽ”›Ÿ¤ª°µº¿¿ÀÁÁÀ¾¼¹³«¢‘soqswz|{{|€‡Ž• ª¯®¤‰ž¦¨¦¨f - - - - - - - - - - - - -#(1G2$%!$%'-138]\[XWVTRQPMLHB - - - - -  e­«®°®ª¥ ˜‚~ƒŠŒ‡†‚|uphebb`bfhjlnsu{„Š‘ž«¹½ÃÈÊÊËËËÊÌÌÌÌËÊÊÈÈÊÈÆÃ¿¼¹µ³²°®¯Ž#$#$ p²­¯°±°®­²ºµc'-,--'Y¸´µ¶¶¸º¹L010.0++k§½¿ºº½¿¿Ã´:&,)&##$…¢™™•‘Їƒ€‚„†‰Ž•›¡¥©®´¸¾¿ÀÂÃÃÁ¾»·­¦›‡vopquy||||„Š‘™¥­±®˜Ž¢©¨¥¨^ - - - - - - - - - - - - &)*-@/!#"$%+115ZXVRRSROMLHDEB! - - -  j««°°¯¬§¢žœ“‡€‰Œ‹ˆ†€xsmhfdcbahikmqruy€†Ž™¬·¼ÁÄÆÇÉÊÊÊÊÉÊÊÉÊÊÈÊÉǾ»¹¶µ´±®²#$#%#t´­°³µ³°°´´·»]'.+,+-“º³³²³¼q,/-,.(5‹»¶´´¶¸ºººº»Ä«P"&!"?˜““ŒŠˆ†…ƒ„…†‰Ž•¡¦«¯³¸¼ÀÁÁÃÄ¿»·²ª|ooruxz|{|~ƒ‡Œ•Ÿ©°±«“£©¨§§W - - - - - - - - - - &())/A,"$!%%)034VSQONOLJIIFEC? - - - - -  fª¬±²°®¨£ ž–ˆ€ˆŒŒ‰‚~wpmifcbaaeimoostzƒˆš©´¼ÀÃÅÇÈÊÊÉÊÉÈÈÉÉÈÈÈÅÂÀ½º·¶¶²±²Œ$&#$#p³­±³´³´´µ¶´·­>'++,&]º±²±´Ÿ4**)+(5𷬮°±²´´³´´´²¶±Lz•Œ‹‰†…„…‡†ˆŠ”œ¡¥«¯³¹½ÁÂÃÄÄľº±ªžrmoswy}}||€…‹’›¤®±±¨†–¤¨¨§¥S  - - - -  - - - ((()(+7*$ $%'/43SSOMLKLIFEDC?=& -  k«¬²³±®«§£Ÿ™Š€†ŒŽŽˆ„|vrkeddcaafklnqru}ƒŠ¬¶º½ÀÃÆÈÉÉÊÊÉÈÈÈÈÈÇÆÆÃÀ½¸¶µ´±³Ž$%$%$q²®²³´³´´³µµ³¹„'-+,*7§´²±·j#)(')&†¶«®¯¯°±±°°°°±®©ª›1P‘‰‡‡…„ƒ†‰Š”𡦩­²·»¿ÂÅÅÅÅÃÀ»µ«ž“ƒspoqvy{~~|~‚‰— ©¯±°¡}„›¥ªª©§N  - - - - -  ))()))+1*"!"'/35QOMJJFGECAAA><. - -  p«­²²°¯­ª¦ œ€€ˆ‘‘Ž‹ˆ‚|wqiggfbbbeimpqsuzˆŽ—£­²¹¾ÂÄÆÈÈÉÉÈÇÇÈÈÇÈÉÉļ¹¶µ±³Œ$%$%#t³®°²³´´µ´³³²³®;++*+*‰¶®±­@"&&&!X°««¬¬­°±°¯¯®¬«§¤Ÿ£g4‡…„†…„…ˆŠ‹‘–š ¦ª¯¶¹¼¿ÂÅÇÅÆÂ¿»µ¬Ÿ•voprux{}~~|…Œ”œ¤­±´¯—|Œž§ª«§£E  - - - - - - *)(()((*3*#!#-44MIGGHED@?@?=>:/ - - - -  r¨¬²³±°­«§£Ÿ˜‡‡Œ‘’Š…€{vojggeda`fhlnorsy~~‡‘¡­³¸¼ÀÃÅÅÇÈÈÇÈÉÈÇÇÈÉÇÅ¿½ºµµŒ$&&(%w¶±²µ¶¶·¹¸¶µµ´»Z$))+&m¹¯´—+&$$$+”°ª¬®¯±²±°¯®¬ª¨¤£Ÿš‰''€………„†ˆŠŽ‘”£¦¬±µ»ÀÃÅÆÇÇÅÄ¿»¶­¥•„uonqtwy}~~}~ƒŠ– ©¯³²«Ž‘¢«®¬¦=  - - - - - #)()(()))*/-" ".33GFFDBAA?>>=<;73 - - - - -    sª«²´²¯««©¥ šŒ‚‚‹’“Œˆ†ztnihhfcbbegjmqsuv|€…‘¢«³¸½ÂÄÆÆÆÈÈÈÉÉÈÈÈÆÈÇÆÄ¿¹ºŽ%()*'~¹²²¶··¸»»¶¶µ³¹n())+'^¹²·~$'#%$Gªª«®®¯±°®­¬¬ª¨¥¢ ™‘’;'|‡ˆ‡ˆ‰‹Ž‘•œ¡§¬´¹½ÁÆÈÊÊÉÇÇÀ¼º²§™†wnmptx{}~}}‡Œ“›¤­²µ²¦…–¨¯¯¬§›; - - - - - - - #')(((*((''2- '/2GCAA?;<<=;<;876 - - - -     s­¬²´´±­«ª¦£œ…‚Š“”’‘Œ‡„yrmjihgebcejmprruz|€„‘ «²·½ÀÂÅÆÅÅÆÆÇÇÈÉÉÉÇÅÄ¿¼¿(-+,+‚¸³µ···¸¸·¶´³²·z&)')'U±«±j $#%!_«¥©©©ªªªªª§¦¤¢¡™•‘“G%}Š‹‰ŒŽ”˜œ£§¬³º½ÂÇÈÉÉÈÆÃ¿»·°§›‹|tkortx{~~~~„‹— ©±¶¶±Ÿ~‡›©®®¬¨›4  - - - - - - $(('(')('''(0-$,/DB>=<988987875/ - - - - - - -   oª«²µ´³°­©§¦ –‡„Š’••”’‰†‚~wrnkjihfdegimppruxz{Š›¨­´¹½ÀÂÄÄÅÅÆÆÈÉÉÈÆÄÄÃÁ¾Å‰-/+-.Џµ¶··¸·µµ¶¶´²¹%)&(%M®¨°W!"% o©¤¨¨©©ª©ª©¦¤¢¡Ÿš–”Ž“I+…Ž‘•—› ¥«¯·½¿ÂÆÉÈÈÇÄÄÁ½¸±¨Œ}ummptw{~€~~‡Ž”›£®³·´¯˜|Œ©­®­©˜, - - -  - - - - - &('''%&&&'''(/+(,A?<<;999844543- - - - - - {©¬±´´³±®«¨©¦™‰„Š’˜˜˜—”‰†‚{upnllkjhgfgkmpqqtvxz„•¡«±·¼ÂÄÄÄÅÅÅÆÇÇÆÅÄÄÄÃÂÈ|-210/–¾¸¹¸¸¹¹¹¸¶¶µ²ºs%&$&#P¬¤ªP #"«§©«©««ª©¨¥£¢¡ž›–“’E3’“••˜œŸ£¦¬±¶¾ÁÃÅÇÈÈÇÅÂÁ¿»´«¢’ulmrtv{}~€€€„Š’™ §°¶¸³©‡{’ ª¯¯¯©' - - - - - - - - -(''&&'&&'&$%&%/0%*<>>=9866543420/ - - -    0ާ®²³´³°¯­ª©§„ˆ•˜šš—’Œ‰„ztollllljgghknqqrtuwyzœ¦±¼¾ÁÁÂÃÃÃÄÄÅÆÄÆÆÆÄÃÉo59:7:¦Áººººº»º¹·¶´±·Z!$#% Z¬¤ªQ!xª¦§§¨©©©§¥¤¤¢Ÿ™–“‘“=?˜•—™ž¡¥¨­³¹¿ÄÅÇÉÉÉÈÆÅÁ½º´¬¦—‡yrmpuwx|~€€‡Ž—ž¤«²¶·³¢‚‚–¥­°²°©Ž"  - - - - - (''''&&&&&%%%$&+-$'::::754420//.-) - - - - - - - -   T›¡­³µµ´±°®«©¨¢‘……‹’•˜˜—•’Œ…‚~ytommmnmkhiilpqrsttvxy~‰—¢¯¶»¾¿ÁÁÃÃÃÃÃÅÇÆÆÃÁÅ_89>=H²¾»º¹¹¸¸·¸¹µ°¯®?$$"$i«£«Zd©¤¦¦¦¤¥¨§¦¥£Ÿœ›˜–”—Š(Wœ—œŸ£¦«¯²·¿ÆÈÉÊÊÊÈÈÅÃÀ¼³«£—†zrnpuxx|€€„‹•œ¢©¯¶¸¶²—|†š¨¯²²¯ª…  - - - - - - - - !('&'''&%%%%%%$$$-0$776753210.-,++* - -  {¡ ¬³µ´³²°¯®«©¢•Š…“•˜™š™•‘‹†ƒ}xsppnmnolkkknprsstuusuz‰™¦´º½¾¾ÀÁÂÀÀÅÅÄÃüO:9TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y- - - - - - - - - -  33-,,,++*+*+*)*)))++**)(()&((&' - - -  - - -  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*3./3A­º¸··¶³³²°®¬«©§¦¦¥¢ž›—“‘‹Œl H˜”w!!ƒ¡ ¢£¤¢¢  Ÿ  Ÿ¢¦®´¹½ÁÃÀ¼»½½¸¬šŒŠ•ž¨¯³²²µ·¹º·³ª–©°³µ¶·¶¯©²¯£F  - ##$##%,,,/0.164.-,+********++*)))**(''((+.0/4956:758;89=@9:;7 B±ÇÎÏÍÉÊÊÆÂ½»¹§—“˜£«±¶¸¸¹¸·¸¹»½¼ºµ®£›’‘˜š˜”‹†„‚€{x(/rrkW}{y%/ššœ ££¤¨œ3$&%'%c·±³µ¹¢1,.,14ƾÁÁÁÁÀ¿Å21,+0T·´µ´²±°°±¯®¬°®®®¬¨¤žš“‘‡†‹‘z$<”“%d¥¢¤¤¤£¡  ŸžŸ” ®°·»½Á¾¼¾½»³¤”ŽŒ‹Ž–£¬±±±²µ¹º»¹²¦‘’ ª²µ¶¶··µ«®¯¥‘…f - - - -$##$$&-/.11-/172-*-+*****,*)()*))***''(),1-.11011345578<<==<5 V½ÇÎÎËÈÊÊÆÁ¾¼¸©š“™¢©®²¶¸º»º¸¸¹¼½¼¶°©Ÿ•Ž”›š—•’‹†…ƒ~}/'suyM#r}|}**„”•“”˜™›¤b  1›¯¬­®®³S&*(+*_¿¹ºº¼¾¾ºÁ}**)(*]µ¯°®®­¬¬«ª¨©s`b_[USKC<81`‘’‚)0Œ•Œ,:¢§¥¤¢ œœœœ JV­¸¶¼¿ÂÀ¼½¾½º¬™Œ‹Œ“ž¨°±±±³¶¹ºº¶¯ •£­´µµ¶¸¹¸­­°©™…z, - - %$$%%)-0/00-,-26.+,,**+**)**(**)))))'(,+-../00//3355565799:95 - vÃÇÎÎËÉÌÉÄÀ¾½º¬”–¡©®²³·º¼¼ººº»¼¼¸°§¢›“‘–››™–’†„„…3"mzvu)E€…‰0*…Ž‘’‹,X¢¢¦§¨§®s$#'&5¡¸±±³¶¸¸¼r%%%#$T®¨¨§¦¦¤¤¢¢¢¡C!Q’‹/(„““@ f«¡¡¡ž››››˜¤w'$R«¿ºÁÀ½»¾¾¼³¤“ŽŽ˜£«±²²±³¶¸»¹³­œ‘—£­´µ¶¸¹¹¸°«¯­¡ˆ€Q - - - &$$%&*///0/-,,-45,++++++++**)))()*)*'&3/000.10038766777596987! &˜ÃÉÏÏËËËÇÃÁ¾¼º¯ –•ž¦¬°´¶¸»¼º¹º¼¼¼º´«¢–’’˜š›™—•ކ‡‡2!o~{}\f‚ŠŽ3+‡ŽŒ‹ŒŒ‘\$|”—›žŸžž’* #j±§ªª«ª«±m""C¥ žžœšš˜™œ›> T‹Œ’4!}‘•_ '~ª Ÿ››š™— ”1"'$D¾¿¾º¼À¾¹¬˜ŽŒ“§¯±±±²´·¸¹·±¨—˜¥­³·¶¸¸¸¸°¨¯±©’€r - - "&$$&(,//0/.--,-.72++++)*(*))))))))**((6324301128;667:88:=8:9:( - =¯ÄËÐÐÌËÊÆÃÀ¾½»²£˜”œ£ª¯´¶¸¹º¹¸¸»½¼¼º°¤›—’“•˜šœš˜•‹‹8"sƒ€~32„‡Œ‘1*„‹ˆ†…ˆ…25‚”˜œŸ¡šžQ2™¢¢¢ ¡¡¤o/Ž“’‘Ž‘–˜“7_ŒŒ’: y’•„'#.~§žšš™– œ='&(&~û¸¹¿Á½¶¥“Ž˜¢«±²±±³µ·¹¹µ¯¥–‘𥮳¶·¶¹¹¸´«°³¬œ€„5 %''&&),...-----,,091,,.,*)**())))()++**<8798549<9:6::<<=AB?@A@7 -  f¹ÄÍÏÎÌÍËÄÂÀ¾¾¼´¥™•›¡§®²¶¸¹¹¹¹¸»¾¾¾»³©—“‘‘’•˜š›š—•’’:!x‹‡ƒŠa^‘“–‘0(€Ž‰‡ˆ‡Žd(6@JOT[`cIh¡—–—™˜›z]‡‚„…‡ˆ‘”–’1_ŽŒ7 v–•žK! )qœ¡›ž¡‹>&%&%9®¼µ¶»Á¾¸­š”𦮲²²²´¶¹º¹¶­£’‘œ§®²µ¶·¸ºº¶­¯³°£‹ˆ[ ((*))+,///.-,-.-,,27.,,-,++*)))))()**)*<<=>AB@CEEFEHHGO - 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“𢍮²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘І}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(();<DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnЇ+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k - *()((&).0/.-././12:82270,++++++++*))*+**BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’𥬲¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+-,SQRSRQPWWWRQQTVUSRTUWVSUTTW9 - PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—𣍬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**+++*[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄÆÂ¹±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†M00.,,00//.-,-./-+,-/0372..--,*,-051-)***PRRWPPRSSTX[RPSRSQROLNPRQPPPNƒÐÌÌʹ¯¼ÉÆÂÃÌÔÖÖÕÒÍÇÅÆÇÍ×ÚÓÆ²š”’˜¢ª±¶»½¿¾¾¾¾ÁÁÁ¿½¹·³­©¦£  ¡ ¡¡¡¢ žœœœœœžžž¡¢¢   ŸŸœ˜–“”••”’‘‘’•˜œ¡¦©®²·º¾ÁÂÃÄÄÄÄÃÂÀ¾½¾½»¹·¶µ´´¶·µ³¯ª¢™ˆ„ƒ€€ƒ„ƒ…†††ˆ‰“™¤¨«®´»ÁÅÇÆÃÁÅÇý²¦›””””‘“Ÿª´¶¶¶µ´µ¸½ÁÄÄÀ»µ¨™Š„‡” ¨¯³µ·º½¾½»º¹¹º¼¼´±³µ¸¹ºº²žŠg/31,,11//0/-,...-,,..132/.--+,,-,/3.+**)TXSTQQSTWVVXPNRTTQSSPNOPNOQNT0 C»ÌÌËŵ±ÀÉÇÂÂËÓÔÕÕÐËÆÃÄÇÌ×ÛÕȶ–’”¥­´¸º½½¾¿¾¿ÁÁÀ¿¾¼»·³®«§¤¤¢¢££¡ ¡  ŸŸ  žž ¡¢£¢¢¡¡¢¢¢¡ ›˜–••––—˜™š ¡£¥ª¯³¶¸»¾ÀÂÃÃÃÃÁÁ¿¾¼º·¸¶³±¯®®®¬«¨¨¦¡œ–‡€€ƒ…„‚‚…„†‹‘•𤍫¯´ºÀÆÉÇÃÃÄÅÄ¿´§““””’‘—¥°µ¶¶µ´³·½ÁÃÿ¹°¡‘…ƒ‹™¤ª®´µ¹»¼½½»¹¸¸»½¼±°´µ¹º»ºµ¥‹Œ~3*1+,/.////.-,-/-----./01-,,--,,+.030*'-UXSRSRWXZVVXPOQSTRPQRQQOOQPPY@ }ËÉÌËŲ²ÆËÈÄÀÉÓÕÕÔÐÉÅÃÅÈËÖÜÕʺ¢—“Ž‹˜¡ª¯µ·»½¿¿À¿ÀÀÁÀÀ¾½º¶³­¬«§¥¥¤£   žŸŸ Ÿ  Ÿ¢£¤¦¦¥¤¥¤¤£¢¡Ÿ›šœœ››œ ¢£¦ªª¬°µº½ÁÁÂÂÂÃÂÂÁ¿¾½º·¶³²³°­ªª«ª§¢ ›˜“‡„ƒ‚‚€~ƒ…ˆ’˜›ž¢¦ª¯´»ÁÅÉÈÄÂÆÇž¶©–’“”’• ­´¶µ´´³µ»ÀÁÃÄÁ¿¸«›Š‚„‘Ÿ¨ª®³·º»¼¾¼º¸¸¸»½º±²¶¶¸ºº¹¶ªˆŽF"//-.00.-..,-./.-,+-/02/,+,*+,,./47.+,TSQQSVTRWSUUPPPQPONPQSSSTUSTYM.«ÊÊÌËÀ¯µÆÊÈÅÁÇÓÕÖÔÏÉÄÂÄÇÍÖÚÕ˼¤–”‘Ž‹Œ’¦¬´¸»¼¾¿¿¿ÁÀÀÁÁÀÁ½¹¶±¯±­©¨§¦¥¢¡žžŸŸ ¢¡¢££¤¥¤¤¤¤£¤£¡¡ŸŸ¡  Ÿ¡¢¤§ª®±´·¼¿ÃÅÅÄÄÂÂÀÀ¿½º¸¶´±¬ª¨¥¤£¢£ œš˜Šˆ…ƒ‚ƒ€}}~‚†ˆ‹’–› £¥¨ª¯¶»ÂÇÉÉÅÂÅÇÄÀµ¨›—”“““Ž“žª²¶µ´³´¶º¿ÂÄÄÃÀ¼µ¥‘…„—£©«®³¸¼¾¾¾»º¹¶¸½½º¯²¶·¸¹¹¹¸®”†Ž_.,,-/.----,--.-,++,.00,*++*,,,,/42-+RPMRUSPQSTSRSPPONRSOPQRQRTQQQS)eÉÉËÌʼ­·ÆËÈÄÁÅÑÕÖÔÍÇÃÃÄÇÍ×ÛÖÍ¿¨™•’ŒŒ˜¡¨°¶º½¾½¾¾ÀÀÁÁÁÁÁÁ¾º¶µ´±­¬«ª©§¥£ ŸŸŸ ¢¡¡¢¢¢  ¡¢£¥¤¤¢ Ÿ ŸŸ ¢¢¥¥¨ª®±µ¹»ÀÃÄÅÆÆÅÄÃÄÂÁ¿½»·µ³±°®©¦¡žš“’‹„}|}„ƒ}~ƒ‡‘–𢤧©¬°µ»ÁÇÊÊÅÂÄÅü²¨œ”“””“’œ§°´¶´³²µ¹¾ÁÂÄÿ¼²žŒƒ‚Žœ¥«¬­³¹½¿À¿»¹¸·¸½½¹¯µ··º¼¹º¹±žŠŠv$,-,../...,,,-..-*+-/.++**,-,+++.4/+SPQUTRVVSSTSTQQRQSQOQNQOQROOOQ=(œÇÇÊËÆ¹«ºÆÊÈÄÀÃÎÒÔÒÌÇÄÃÅÇËÔÚ×Ï«œ–•’Œ“›¤«²¶¹¼¾½¾ÀÀÁÁÀÂÂÁ¿»¹¸¶´´²¯­¬¬ª¦¤¡ ¡¡¢¡    Ÿ¡¢££££¢¡ ¢£¡¢¡£¥©¬®²µ¸»¿ÂÄÅÇÇÈÆÅÅÄÃÂÀ½º·´²°®®­«§¢›–‘Žˆ†~~……ƒ€„†‹‘•–™œ £¦¨©¬°³º¿ÆÈÉÅÂÃÅû±ªŸ–““”“ŽŽ—£­³µµ²²´¹¼ÁÂÂÂÁÀ½¹®š‰ƒ‡•¢¨¬¬­²¹½ÀÀ¾¼¹·¶º¾¿¸°³¶·º¼»»ºµ¤Œ…€=).////0..----+++,-.,*,,*,,*,,*,44-ƒ‚…ƒ€‚ƒ€€€‚„…‚~|~€€€€€€€€‚ƒ„ƒ€€‚ƒ‚‚‚‚€€€€„†{|}}}~~||}~„ƒ€€~ƒ‚€ƒ‚‚€‚‚€ƒ€€€€‚~€€€€~‚‚}~‚†‚~|~‚~zƒƒ‚‚ƒ‚‚‚€€‚‚€‚ƒ€€‚€€€€€€€‚€€€€€€€€‚‚€€‚‚‚‚ƒƒ‚€€‚ƒ…‚zwy{{zz{zzyx‚ˆ€€€€€€‚‚€€€€€€€‚ƒ‚€€‚ƒ€~€€€€€€€ƒ‚‚„„„}~…~tƒƒ„‚‚ƒ„‚€€€€€ƒƒ„€‚ƒƒ‚€€€€‚‚‚‚‚‚ƒ‚€‚‚€ƒ„ƒ‚‚‚‚‚‚‚‚ƒ‚‚€€‚‚……‚‚€ƒ‡€‚‹ˆ…€€€€€€€€€€€€€€ƒƒ‚€€€€€€€€€ƒ€~…ƒƒ€~ƒy„…………ƒƒ€€ƒ€ƒƒ‚€‚€€€€‚„ƒƒ„„„ƒ€€‚„ƒ‚ƒ„ƒ€‚ƒ€€‚‚€€€‚†…€ƒ„ƒƒ‚€…†ƒ€ƒ‚…†„„ƒ‚€~€€€€€€€€€‚€€‚‚€€€€€€€‚‚€„‚ƒ~€‚€„ƒ‚‚„ƒ‚€„ƒ‚‚‚€‚€€€‚‚‚€‚ƒ„ƒƒ‚€€ƒ‚‚‚€ƒƒ‚‚‚ƒ‚‚€‚ƒ‚€‚ƒƒ‚„‚€ƒ„„ƒƒƒ€€€‚‚wy€…††ƒ‚€€€€€~€‚€€€€€€‚€‚‚‚€€€€~€€€€ƒƒ~€€‚ƒ†„‚€€‚ƒƒƒ‚„„ƒ„‚‚‚‚ƒ‚‚‚‚‚‚‚‚‚‚ƒƒ‚‚‚ƒ……ƒ€€‚‚‚ƒ‚‚ƒƒƒƒ‚‚‚‡ƒ‚‚ƒƒ„„‚€~€…†€~z|‰†‚€€‚‚ƒ€‚€€ƒƒ€€€„ƒ€€‚€~~‚€€€‚‚€€€‚„ƒƒƒ€~€€€‚ƒƒ„ƒ‚‚‚ƒ„„„ƒƒƒ„ƒƒ††ƒƒ„ƒ‚‚‚ƒƒƒƒ‚‚„‚‚‚ƒ„ƒ‚ƒƒƒ€‚‚‚€€‚‚ƒ‚ƒ‚‚‰„‚ƒ…‰‰……„ƒ†…€„‡ƒ}†…€‚‚€‚‚€ƒƒ‚‚€‚‚‚„„€€€€‚‚‚„‚€€„†„…„€€‚ƒ‚ƒ…ƒƒƒ‚‚ƒƒ„€„„ƒ„„‚„ƒ€ƒ‚‚ƒƒ‚€€ƒ‚ƒƒƒƒƒ‚ƒ„ƒ‚‚„„‚‚ƒƒ‚‚І„ˆ…ˆ‡…„ƒ€…‚††„€ˆƒ‚„ƒƒƒ‚€€€€€ƒƒƒ‚ƒƒ€€‚‚‚‚€€‚‚‚€‚‚‚ƒ‚‚‚„‚„„€€‚€€€ƒ†„‚ƒƒ†…ƒ‚‚ƒƒƒ‚‚ƒ„ƒ‚‚ƒ‚‚ƒƒƒƒ‚„„ƒ‚ƒƒ„„‚‚ƒ„…„ƒƒ‚ƒ‚‚‰„€€„‰‚‚ƒ€†‚†„‹‚„ƒ‚‚€€€€€€‚‚€ƒ‚‚‚‚‚‚ƒ‚€€€‚‚ƒ~ƒƒ€€‚…„‚€€…„„‚ƒ„„ƒ‚‚‚‚ƒƒ‚‚………„…‡†‚ƒ‚‚€€‚ƒ‚‚ƒ‚„…ƒƒƒƒ€‚ƒ‚ƒ…„ƒƒƒƒ‚ƒƒ„‚‚ƒƒ‚ƒƒ‚ƒƒ‚‚‰ƒ„‚‚€}‚‚‚‰ƒ€††ƒ„ƒƒ‚€‚‚ƒƒ€€‚‚ƒƒ‚‚‚‚ƒ„ƒƒ‚‚‚‚‚~€†„‚„‡†„ƒ‚€‚ƒƒ‚‚ƒ‚ƒƒƒ‚€††…†…ƒ‚„‚‚‚‚‚‚‚„ƒƒ„ƒƒ‚€‚„„ƒƒ‚‚ƒ„…„ƒƒƒƒ„ƒƒ„„ƒƒ„ƒƒ‚‚„…Š„ƒ„„€€ƒ}‚‚ƒ‚‰„ƒ„‚„‰‚ƒƒ‚‚‚€€€€‚ƒƒ€€‚ƒƒƒƒƒƒ„ƒ‚€€€€‚ƒ~ƒ‚‚ƒ……‚‚‚ƒƒ„„‚€€‚ƒ‚‚‚€€„‡‡‡†…„„‚€‚ƒƒ‚‚€‚‚ƒƒ„„ƒ„ƒ€‚‚ƒƒ‚ƒ‚ƒ„ƒ‚‚ƒ‚ƒ„„ƒƒƒƒ‚„ƒ‚…‹„‚ƒ„„ƒ‚ƒƒ‚„„Š…„„‚ˆ‡‚ƒ‚ƒ‚‚€‚ƒ‚‚ƒ‚‚‚‚€€‚‚‚ƒƒƒ‚ƒƒ‚‚‚‚€€‚€€€€„…††ƒ€€ƒ„ƒƒ†…„€€‚ƒ‚‚„…ƒ€€ƒ†‡…†„ƒ‚ƒ‚„ƒ‚ƒ‚‚‚ƒ„ƒ‚‚ƒ„ƒƒƒ‚ƒƒƒ‚‚‚‚ƒƒƒƒƒƒ‚ƒ„ƒ‚ƒŠƒƒ…†ƒƒƒƒ„„…†Œ„ƒƒƒŠƒ„„‚‚‚‚‚ƒƒ‚‚„„‚ƒƒƒ‚‚ƒ‚‚ƒ„ƒƒƒ‚‚‚‚€€€€€€€€‚††…„‚„…‚‚†„ƒ‚‚€€ƒ„„‚‚‚„……†ƒ„ƒƒƒ‚‚……‚‚ƒƒ‚‚ƒƒ‚‚‚‚„„„„……„‚ƒƒ‚„…†……„„„„ƒ„Š‚ˆ‡‡‚„ƒ‚‚ƒƒ„„‚†‰…††„„ƒ‚‚‚€€‚‚‚ƒ‚‚‚ƒƒƒ„ƒƒ‚‚ƒ‚‚‚‚€€€€€€€€€‚††…ƒ‚‚ƒƒ„ƒƒ„…„‚€‚‚€‚ƒƒƒƒ………†‡‡…ƒƒƒ‚‚ƒƒ„‚‚…„‚‚„…„„ƒ‚‚‚‚‚‚ƒ„ƒ„……„ƒ‚‚„„„„ƒƒ„……„„‚„Š€…‡ƒƒƒƒƒƒƒƒ‡Ž…ƒ‚ˆ„‚…†…ƒ‚ƒ‚‚‚€‚‚‚‚‚‚‚ƒ„‚€€‚‚‚€€€‚‚‚‚‚€‚…‡†„„‚‚†ƒ‚‚€„„…ƒ€€€‚‚‚ƒƒƒ……†…ƒ„„‚ƒƒ„ƒ„ƒƒ„„‚‚‚„„ƒƒ‚‚‚ƒƒ‚ƒƒ‚ƒƒ‚ƒ„„„……ƒ„„ƒƒ„„„„„…„ƒ†Š‚…†‚ƒ„„……„„ƒ†Œ„‚…‰ƒ„ƒƒ‚ƒ‚ƒƒ‚€‚‚‚ƒ„„ƒƒ‚‚‚‚‚€‚‚ƒ…„ƒ€„ƒ„ƒ‚‚‚€‚ƒ‚ƒ‚ƒ‚€€€€€ƒ‚‚‚‚……†„ƒ…†…‚ƒ„‚ƒ‚ƒ„ƒ‚ƒƒ‚„„ƒƒƒ„„„ƒƒ„†…„„„„„…„„„…„ƒ„„„„„……ƒƒŠ‡‡†‡‡†…†‡‡†„‡Œ„‰ˆƒ…ƒ‚‚‚‚ƒ‚„„‚€‚‚‚„…„ƒƒƒƒ‚‚‚‚‚€€€€€€‚…ƒƒƒ‚‚€€‚ƒƒƒ‚ƒ„ƒ€ƒ„ƒ„„ƒ‚ƒ……€‚ƒ€€€€‚‚‚ƒ„ƒ„……ƒƒƒƒƒƒƒ‚‚„…„„„ƒ„„‚‚ƒƒ„„„ƒ…†„„„ƒ‚€€€„‡ˆ†„………„„‚‚†„„………ƒ‚‚€‚€€‚ƒ‚‚‚„ƒ‚‚ƒ‚ƒ„ƒƒ‚€€€‚€€‚„„‚ƒ‚‚€‚‚ƒ‚€€‚„‚‚€€‚ƒ‚€€€ƒ‚€€€ƒ„‚€€ƒƒ„…„ƒ„„ƒ„„ƒ‚ƒƒƒ„ƒƒ„„„„‚‚ƒƒƒ„„ƒ‚ƒƒƒƒƒƒƒ„~{zz||}€~}|~€…Š‚ƒƒ„…„‚‚‚€‚‚‚‚‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒ‚‚€~€‚‚ƒ€€€‚„ƒ‚ƒ‚‚‚€€‚‚‚‚€€‚ƒ€~€‚ƒ‚ƒ…„„ƒ‚‚„…„ƒ„…„„…ƒƒ……„ƒ‚‚ƒƒ„„…†…„„ƒƒ„ƒ„ƒƒƒ„„ƒƒ„……„„……„ƒ‚zz~€…‡††…†‹„„„„ƒ‚‚ƒƒ‚‚ƒ„„…ƒ‚ƒƒ‚ƒƒ„„„ƒ‚‚‚ƒ‚€€€€€€€€€ƒ††€ƒ‚€€€€€‚ƒ„ƒ„‚€‚€€€€‚‚‚‚ƒ„‚ƒ…‚ƒƒƒƒ„††………„ƒƒ„„„„ƒƒƒƒ‚ƒ„…„ƒƒƒ„††……‚ƒ„„…†………„……„ƒƒ„…„„…„ƒ{{{}‚†…‚„„„ƒƒƒ‚‚‚‚„„……ƒ‚ƒ„ƒ‚‚ƒƒ„ƒƒ‚‚ƒ‚€€€€€€~€‚„‡†ƒ‚€€€‚‚‚‚€‚ƒ‚€€€~ƒ…„ƒ‚ƒ„„„„„„„ƒ„…„„…„ƒ„ƒ‚‚ƒ‚ƒ„…„‚‚ƒ………††ƒƒ„„„„„††…„‚ƒƒƒ„„ƒ„………„…ƒ‚}{~ƒ………„ƒƒ„ƒƒ‚‚„……„„„„‚‚ƒƒ„„„„ƒ‚€€€€‚‚‚€€ƒ…†ƒ€€€‚€€ƒ‚€‚‚‚€~€€~€~€}ƒ‚|‚„„„„„„ƒ„ƒƒ„„ƒ‚ƒƒƒƒƒƒƒƒƒƒ„„‚‚„†…„…„„„…†„‚ƒ„……„ƒ„„„„‚ƒ…„ƒ‚‚„…„ƒ„†‡†…„ƒ„ƒƒƒ„„„ƒ„…„…„„„ƒ„ƒƒƒƒƒƒƒƒ‚€€€€€€‚‚€‚ƒ‚ƒ~„……ƒ€€€€‚ƒƒ‚ƒ€…„€‚„…‚‚€€€€ƒ€€‚}|‚„……„ƒƒƒƒ„„‚‚ƒƒƒƒ‚ƒ‚„„ƒ‚ƒ‚‚ƒ„„ƒƒ„…„………„…„„„„ƒƒƒƒƒ‚‚‚ƒ……ƒ‚‚„„ƒ„„„…„ƒ‚‚‚‚‚‚‚ƒ„ƒƒ„„ƒ„ƒƒƒƒƒ‚‚‚‚‚‚€€€€€€~€€€‚„ƒ‚€€„ƒ„‚€‚‚ƒƒƒ‚ƒ‚€€ƒƒ‚‚‚ƒƒ€€‚€‚€€€}}‚ƒƒ„„„ƒ‚‚ƒ„„ƒƒ„ƒƒ‚„ƒƒƒ„„ƒ‚‚„ƒ„ƒ„„„ƒ…††……………ƒ‚ƒƒƒ„„„ƒ„„„„„…„„„„„ƒ„……„„„„„ƒƒ‚‚ƒ„„…„……ƒ„ƒƒ„ƒƒ‚ƒ„ƒ€€€€€€€€‚‚‚‚‚‚}ƒ…‚‚‚‚‚‚ƒ„ƒ‚€€‚ƒƒ€~‚‚€€€€€~~{|}ƒƒ…†…„ƒƒƒƒ„ƒƒ„„„ƒƒ„„…ƒƒ‚‚ƒ„„ƒ„…„„…„‡‹†ƒ„ƒ„„„††……………„„„ƒƒ„……„„……††…†………„„„…ƒ„…„………†…„„…†…„ƒƒƒ‚‚‚ƒƒ‚‚€€‚‚€€€‚‚}„„ƒƒ‚ƒƒƒ…ƒ‚€‚ƒƒ€‚„ƒ€€€€€€~‚ƒƒ‚ƒ„†‡†…„„…„„„„„ƒƒ……„ƒƒƒ„„ƒƒƒƒ„„…†„‡Š„ƒ‚ƒ„…†Š‹Šˆ„„‰‹Šˆ‡†††‡†„„‡Š‰‰ŠŠ‰‰ŠŒ‰…„ˆ‹Šˆˆ‰ˆ‡„‚ƒ………„ƒ‚‚‚‚‚€€€€€€€…‚€‚‚‚ƒ„ƒ‚ƒ‚‚€‚‚‚„€ƒ€€ƒ}}€€‚†‚‚‚„„ƒ‚„†…„„„„ƒ…„„„„ƒ„†…„ƒƒƒƒ‚‚ƒ…„……„…†ƒ„ˆ‰‚}ˆ‰†…‡‰Š‰‰‡‚ˆ‡‚€‚ƒ‡ˆ‚€ƒ„†…ƒƒ‡‹‡ƒ„„„„ƒ‚‚‚‚ƒ‚‚€€€‚€‚~‚€‚ƒ€ƒƒƒ‚€€„…~€|{€€€ƒ€‚„†‚€€€€„…„‚‚ƒƒ„ƒ„…††……„„„„„…„„‚„…ƒ„„…†‡ƒ‚‚…‹Š…|~ƒƒ„‡…}€€€€‰…€…ƒ~z{|}|„€|€~|}ƒŽ‰ƒƒƒ„ƒ‚‚‚ƒƒ€€€€€€€€„‚€‚ƒ……ƒ‚‚‚‚‚€€€€{ƒ€~}€€€€€€€€‚ƒƒƒ„€€ƒƒ…†„ƒ‚ƒ……„„…„„„„„……„……„„ƒ„„„„„„…‰„ƒƒ…Œˆ‚|€„………Š…{|}{z{€‚…‚„|~~}{|€€~~„|x|ƒŽ‡„ƒ‚ƒƒƒƒ„‚€€‚‚€€€€€‚€€‚ƒ„…‚ƒƒ‚‚‚‚€€sz}}|€~€€€‚‚ƒƒ€ƒƒ{~„ƒ‚ƒ„ƒ‚„„„„„‚ƒƒ„…†…ƒƒ„ƒƒ…„ƒ„†„„„‰†‚†Š‡}|…ƒƒƒƒ‡…‚ƒƒ‚‚ƒ‚„„‰‡‚ƒ‚‚‚ƒ‚~ƒŠ‚ƒ‚{|Љƒƒƒƒƒ€€€€‚‚€€€€€€ƒ…‚‚„ƒ‚‚‚ƒ‚€rsx|}}€~€€€~€ƒ‚€~yw‚ƒƒ„ƒƒ‚ƒ…†ƒ‚„„„„††…„„ƒ‚ƒ„………†…„ˆ†„„‚}|…„ƒ‚‚‚‡‚ˆ†ƒƒ…„„„…„…ƒ††ƒƒ‚ƒ„„……€€…†‚……~~ˆ‰ƒ„„ƒ‚€€‚ƒ€€€€€ƒ‚€€‚ƒ…ƒ„„‚€ƒ„‚€sqvy|‚‚€~€€~€}}||{z‚ƒ„„…‡‡…„…††…†…„…†††„„„„ƒ„†…††…„†…€~€…†„ƒ„„„„ƒ…ŠƒˆŒˆ‰‰‰‰‡…ƒ†ƒƒ†‰‰ˆˆ‰‰‡ƒ€€…†ƒ„…ƒ„~‰†……‚‚€€€‚‚€€€€~€‚€~€‚„ƒ„„…ƒ‚ƒ€sqsux€‚‚€€~~~~‚}{}~}~|}ƒ…………†††††‡‡†††‡‡‡†……††…„…„…„ƒƒ‡…€ƒƒŒˆ‚…†……„‰‹…†ˆ††‡ˆƒ‚‡ƒ€‚„„……†‰…€€‡‡ƒ„„†„ƒˆ‚‚„„‚‚‚‚‚‚€‚‚‚€€€€~|~€€€„ƒ€ƒ…ƒ€psuvv{€ƒ|{|}~€||‚‚ƒ„„„„…‡†„„……†‡‡‡……„…„ƒƒ…„ƒ…„„‰„€„ƒƒˆƒ…‡†…ƒ‰‰}~~}}‚‚ˆƒ~}~}~~…ƒ€…‡ˆŠ‹‰…†‚ƒ‚ƒƒ€€€‚‚€€€€€€€€~{{€€€‚€€ƒƒƒ€|{osuuuv{€~‚€~{z|}|{~|z€‚„ƒƒ„…†…†‡…„……………†‡‡††„‚‚†‡†…„…‡„€€‚ƒ„„††…„„‚ˆˆ„…ƒ~}€€ƒ…~€€‚€~„‡„‚…‚€‚‚‚‚‚€€€~|~‚ƒ€€ƒƒ‚ƒƒ€}}rqrsxwxz€‚~~}|{{}~~~}|z}‚ƒ…„…††‡†††…„„†…„†‡††……††„…†††……†€€‚„„‚‰……††‰‡„†††‡†‡‡††„ƒ…„„…„„„ƒ‚ƒ„€€€‚ƒƒƒ‚‚‚‚‚ƒƒƒ‚‚‚€€€€€€€~~€‚„ƒ‚}}€„…„ƒ€stsuwttv|ƒ}}~}~}}|{}~|y|ƒ„„…†„ƒ…‡†…‡†…„ƒƒ………………†‡†„ƒƒ…†‡…~€‚†……ƒˆ‰………†ƒ‚„…†‡ˆ‡†‡‡„‚…„„„„…„„ƒƒƒˆ…€ƒ„…ƒ‚‚ƒ‚‚ƒ€€‚ƒ‚€€‚‚ƒ€€~|}}‚‚€~~~~€…„~€~uvvtsux{z|~|}}}|~~||||}|y{„†…ƒƒ‚‚…††‡‡†…†‡†………†…„…†…„„ƒƒ…ˆ…ƒ„†ˆ††ƒŠ††„„‡ˆˆˆ‡†…ƒ‚‚‡†…„„…„„ƒ‚„Ї‚„ƒƒ‚ƒƒ‚ƒ‚‚‚‚‚‚‚ƒ€€€€ƒ€||~~{|}~ƒ‚€€€~~€€€ƒ…€|}€‚vsstw{{}€~}~~~{||}|}~}}~{y‚…ƒ‚„…………†……„………†„‚‚‚‚ƒ„„„„ƒƒ†…‚€‚…†ˆ‡†„ƒ††…‚~ƒ„„…†ˆ†‚€€„†ˆ‰ŠŠ‹‰„‚…‹…‚„ƒ‚‚ƒ‚ƒƒ€‚€€€€€€~~}{{}}{}~€€€‚~~}~{~‚utuy}€~~}€~||~~~~~}}z‚†„…††††„„…††‡†„††ƒ‚ƒ‚ƒ„…†„„…„……ƒ„††‡†ƒ„„…‚~~~€ƒ†€„‚€ƒ…„‡‹ƒ‚…‰„‚„„„…‚‚ƒ‚€‚€€€€€€€€€€~~|}ƒƒ‚~~}}€~~€su{~‚€~~~}~~~}}€~}|}~„†ƒ„‡†…………†‡‡‡‡††„‚‚„…„„‡ˆ‡‡††‡‡†††………ƒ„ƒƒ„……„‚€€†…„…€€€€ƒ‡„‚€„…„ƒ„ƒ€‚‚‚ƒ‚€€€€€€€€€€€}|€„„€€€~„„‚€„ƒ„vzz|}|}‚}{|}~|~|{}~}~~{‚‡ƒƒ†‡„ƒ„„………†……„‚ƒƒ„„…†††‡†…†‡‡††‡‡‡†…„ƒƒ„„†…„††„„†„‚„…„„ƒ‚€‚€‚„…ƒƒƒ„„……„ƒ€€€€€‚€€€€€€€‚}€€€~}~€‚€€€€ƒ„ƒ‚€€‚†…ƒ}}{|€€||y}~}}~}||}}~~z€…ƒƒ„…„„„…‡†„„ƒƒ„ƒƒ„…†††‡ˆ†„„„…„„„ƒ„„„……ƒƒƒ„„„…††ƒƒ„„ƒƒ„……„ƒ‚ƒ„„…„„„„……„ƒƒ„……„‚‚~‚ƒ‚ƒ‚€€‚‚€€}~€}~€ƒ‚‚€€~€‚„~€€‚„„ƒ€~‚~|z{~~}}~~~~|}{~ƒ„†††††‡‡†……„„……„…‡††‡‡‡…ƒƒ„……„ƒ‚‚„„…„ƒ„„„…„„„„„…„ƒƒƒ„„„„„„…„„……††…„„……††„‚‚‚‚‚‚‚ƒ„……‚€€€€€€€~|ƒ‚€~~€ƒ‚€€‚ƒƒƒ~~~~}}~|~}~}}€~}~|}|~{z€‚…†††‡†……†…ƒ„………†……„††…††„†‰Š‡‡‡‡‡…ƒƒ„„„………„„„ƒƒ„„……„„„ƒ…††…„„…†‡†…„„„……„ƒ‚ƒ…‡†‚‚„„ƒƒ„ƒ„ƒ€€~€€€~{{~€€€~€€€€‚ƒ‚‚‚}}|{|}~|~}€}|{|}}{}}}~}~}|x~‚ƒ‚„„…†……„ƒƒ„„„„„ƒ‚‚ƒƒ†Œ‰‚€ƒ„ƒ…‰‰†„„„…„„ƒ„‰ˆ†„„…„…†…„…†††……†††…ƒƒˆ‡‡…€€€€ƒ„†…„„……„ƒƒ‚‚€€‚€€€~~~}z|~}}~€€‚€€‚ƒƒ‚~|z|~€~||||}|||{|}|||~~||}‚ƒ„„„„„„ƒƒƒ„………„ƒƒ„„„‰‹„€~€€ƒ‚„‹†„„„„…„‡‹Œ†„…„„……ƒ‰Œ††‡…„„„ƒ‚†‚‚…€„€‚€„†„„…„ƒ„ƒ‚€€€‚€€€~{{~}~~|€€€‚€€~{|€€}|~}~}|~||~~}}}~}~}~€‚‚ƒ„„ƒ„„ƒƒƒƒ„……„…†…††ŠŒƒƒƒ‚‚††ˆ†„……„ƒ‡Šƒ‚ˆ‡„†††…†„Šˆ‡„…††…†„ƒƒƒ~ƒ†‚ƒƒ‚€‡‡„„…„ƒƒƒ‚€€€€€€‚‚~z~|€~~~€€€‚‚ƒ…ƒƒƒ}}||z~}||{z~}}||}||~~~}~|}‚‚ƒ‚ƒ„„„ƒ‚‚ƒ„…………††…†‡‡Œƒ}€…‡………ƒƒ…‚„„……„„І„‰‚………†„ƒ‡ƒ…ƒ„…………ƒƒ†‚ƒ€€~††‚‚ƒƒ„ƒƒ‚€€€€€€€|}ƒ€{}}~€‚€€ƒ‚€€„……}~{{}z}€~}€}|~|}~{}~~|}~}|~ƒ…‚ƒ……ƒƒ„ƒƒ„…†††††…„…ˆ‰…†††………ƒ€‚………‡…††€€‰‡…„„…„ƒ…‚‡„†‡†……„‚‚€……‚‚„€€††„ƒ‚„„ƒ‚€€€€€‚‚{{‚€}€€€‚ƒ‚€€ƒ‚z}}}~|||}~~|}}|||{{}€~~~}}‚ƒ‚„„„ƒ„„……„……………†††…‰†€€ƒ…††‡……††……………††Š„ƒ‚ƒ‡‰…„„…„„…†‰ƒ†„„„„„„€€†ˆ‚„ƒ}ˆ†„„„ƒ„„„„‚€€€€‚ƒƒƒ„ƒy{€}€€~~}~€‚‚„……„‚‚~~~|}}{|~}~~~~}|{}}}€~||~€‚„„„„ƒ…„ƒƒ„„…†‡……††ˆ‡€‚„………………†‡†…†††‰‡…†€ƒ…ƒˆ„„†…ƒ„„†‰ƒ„††…„ƒ‚ƒƒ€„ˆ„…‡‚~ƒˆ…„ƒƒƒƒƒ„„‚‚‚‚ƒƒ…ƒ|z~}}}}~~~~~~~~„‡‡†„€€~~{|}~}||}~€~|}~}~€€€}}~ƒƒƒƒ„„„…„ƒ„„…†………†‡‡†ˆ„„‚‚„…††…†………………Ї€ƒ‚ƒ‚…‚‡ˆ†…ƒ††„‰…†……„„ƒƒƒƒ~€ƒ…Š€ƒŠ†ƒ‚‚ƒ„„ƒ€‚ƒƒ‚ƒ‚|€~}€~~~~~‚‚‚‚ƒ„‚~~~~}}{~}|}~~}{~}|}~~~~~~z|„ƒ‚‚ƒƒ………„ƒƒƒ„††‡ˆˆ‡„††…ˆ„…†††………‚„…„ˆ‹ƒ‚‚ƒƒ„…‡Š„„ƒ‚†ƒ‚…ƒ…†…ƒƒ‚„„‡‚€~†‡~€‚‹…‚‚ƒ‚‚‚€‚‚‚ƒƒƒ‚€}}~~€€~}~~~€€‚„~||}~~~~€~|}~}~€~}}~~}x|ƒ‚‚„„ƒ……„„ƒ‚ƒƒ„…†††‡†„‰†ˆˆ„‚ƒ„„‡‰„…†‰ƒ€ƒ††‡‡†ƒ‚†„ƒ„„‡…†…„„„ƒ‚ƒƒ„‰†€‚„Œƒ‚‹ƒ‚„„„„‚‚‚€‚ƒ„„ƒ„…„~|}~~}~~€‚ƒ~~€‚ƒƒ‚ƒƒ|{|~}}~~€€}~~|}~~~}}{|{~y|ƒ‚ƒ„„„„„„ƒ‚ƒ„„„„„†‡†…†ˆ„„ˆˆˆˆˆ‡†Š†„‹†ƒ„††‡‡†‚„ˆƒƒ‚…†…ƒ…†„ƒƒ‚†ˆ‹‚‚ƒ‰‡‚€‚„ƒ„„ƒ„ƒ‚‚‚ƒƒ„ƒƒ„…|}~}~}~‚„~€‚‚‚€‚†…ƒz{|}}|{}~|~}}~}}}~}||~~~~~z~‚ƒƒ„ƒ„………„„„„……„…†‡‡†…„„ƒ†ˆ‡…„…††Š‚ƒƒ‚……„„ƒ€ƒŠ„„‚†„ƒ‚ƒ…ƒ‚„ƒ€†‡Œ‚ƒ€„Šƒ€Œ…ƒ‚ƒƒ„ƒƒ‚‚ƒƒƒ‚‚ƒ‚ƒ„ƒ|~„€~€€~~|~€ƒ~€€€€€€€‚‚ƒ‚ƒ|z|~~}}~~}~|~€€~|||}}~}|~€€y~ƒ€ƒƒƒ‚ƒ„…„„………………†††…‡†ƒ„„„‚ƒ„…„‚…Œ‡‚‚‚ƒ‚‚‚…†‡‡„‚†‚ƒ†‡‡…„Šˆ€…„‹†„ƒƒƒ€ƒŠˆ‚ƒƒ„„ƒƒƒƒ„ƒ‚‚‚ƒƒ„„y€€~€€~}|~}€ƒ~~€€‚‚}~|{|}}|||}|||~}}~~||}~~~~~zƒ…ƒ„ƒ‚ƒ„„„„„„†……„…………„……†„ƒ„ƒ‚ƒƒ‚„…ˆ„‚‚ƒ……„ƒ„…ƒ‡‡„…ƒ‚ƒƒ‚ƒƒ‚ˆŠ€…ˆ‹…„„ƒƒƒ„~„‹ƒƒ‚„…ƒƒƒƒƒ„ƒƒƒƒ„…ƒ~y}~~~}||}€}|}~}€‚ƒ€|{{}~}||{{|}}}~~}~€~~}~}~~~zzƒ…ƒ‚‚ƒƒƒ„„„„„……„„„„……„……††…††„„……†…ƒ‚‚ƒ„…„„ƒ„…„…„ƒ†„„„„‚ƒ„„ƒƒˆˆ‚†ˆ‡ƒƒ„…„ƒ„„‚…ˆ…‚ƒƒ„ƒ„„„…„…„„„„„ƒ}x~}~~}~€~}~}}~}z{€~|zxy€€|{~|~~~}~~}}~~}~~~}}zx‚…‚ƒƒ‚‚‚ƒ„„ƒ„„…………„„„„………†‡‡††‡‡†…†…„ƒƒ„„ƒ„…„„†„ƒ‚ƒƒƒ‚‚‚‚ƒ„„ƒ‚ƒ„„„……ƒƒƒƒ‚‚ƒƒ„„„ƒƒ…„ƒ„…ƒƒ‚}~x|~}}~~}}€~}~€€~|z}~}|{{xusyy||€‚‚||}}}~}||~~~}€~{y‚†‚ƒ‚‚ƒƒƒ„„„„…………„„„…„ƒƒ…††††††…………ƒ„…„ƒ„„……„……„„……„ƒ„„ƒ‚‚‚„„„…„„‡†…ƒƒƒ„ƒƒ„„ƒƒ„………†…ƒƒ„„„„ƒ‚‚x}~~€}~}}~}~~€}zzyyzyyyvuuuuvwux|ƒ~{}}~~~}}}~~~}}{y‚†‚‚‚ƒƒƒ‚‚ƒ„……„…††…………………„„……†‡‡†……„„ƒƒƒƒ……ƒ„…„……„ƒ„„„„„„„……………„……„„…ƒ„………††…†‡‡†‡†…………„ƒ‚ƒx}~}~~}}}~~}~}||{{zyyyxwwvuswxzyy{€~}|}~|{||}||||}}~||€~yx‚…‚€€€ƒ‚ƒ„„……ƒ„…„„…†…………………†…………††‡†††„ƒƒƒ‚‚ƒ„„…†††„ƒƒ„……………††††……„„………†„„……………„†‡†…„…†……„„„ƒ‚~ƒ…w~€€~}}}~}}||{yyzywwwvuusxxz{x{‚~}|{}{|}~~}}}}~~€}~€{x……€€‚„………„……„…†……………„……„††……„„…††…ƒ„„‚ƒ‚‚ƒ„……ƒƒƒ„ƒ‚„„„ƒƒ…†…„„………††…„„„…†………„„…………………††„ƒƒ}~ƒƒv}€~~}}}~~~|z|yxyywxxuuvtxxyxx{€}|}|}€}~~}}zw‚††‚‚€ƒ„„ƒ„……„††…………†…†…„……†…„„…††††……‚ŠŒƒƒ…ƒˆˆƒ‚ƒ‡†‚‡Š‹Š†‚„„…………„„„„„„†‡††…„„…†‡‡†„„„„„‚‚}}u{~€~~~~}}~}}{{{zxwvxvussrux{xuy€~~}~}~}~}}}~~~~~}}}ywƒ…„‚‚€„„„ƒƒ………………………†„„…………††…ƒ„……„…………Љ‡‚„Š€ŒŒŒ„…††……………„„………††……†…„„„…„„„„ƒ~~€‚w|~~€€~€~~}~€~|{xxwvwwusrrwwxvvz€€~}|}~|}}}~}~}}|{{}wxƒ……ƒ‚‚„‚€€‚„„„„„„„„„„…†††††…‡†††„„…†„ƒ„„„‡‰„‰ƒ‚‡‰Œ‡‹‰‹‚ŽŽƒ„…ƒƒ„………„…†…††……„„ƒ‚‚ƒƒƒ„„ƒ‚€ƒ„‚}z~~~~~€~~~}~€~€|||yxwwwwuspqyxvtuz€€~~{}€~}}{|}|~}||}wzƒƒ†„‚ƒ„ƒ„…„„„ƒ……………††…††††‡†…„…………„„„„…ІƒŠ†€‡‰…Žˆƒ‹†‹‚‚€‡„…„………………†‡††…„„ƒƒƒƒ„……ƒ‚ƒ„‚€„ƒy|~}~~€}}€~~~~z{|zxxwwvsoszyxvvz‚|z||}€~~€~||~~~w{ƒ…‡ƒƒƒ‚…†‚„„„……†…„………†…„„„„…‡………††…„ƒƒ‚†‰„ƒ‡ˆ„†ˆ†‹†ˆ‚„Š‚€…އ„„„„„„„„…†…„„„„„ƒ‚ƒ„†††„ƒ‚€€€‚‚‚{{~}}}€€~}}€{|~zyzzvsrrsyxxyyz€}||{}~}|}~{|~~}}x{‚…‡„ƒ‚‚ƒ„ƒƒ‚‚ƒ„…†………†††…„„„„„††…„……††††…Šˆ††‡‡‡‚†‰„‚ˆ‡ˆ„„Œˆ†‹Ž‰„„†…„ƒƒ„„„„…„…†…††…ƒ…†‡††„„~…ƒ‚‚ƒ{{~~~}}~~€~~~}zzz{ywvtuttxxwwvz~z|}~|}}{|}}|}‚€‚€y}‚ƒ…„„‚‚ƒ‚‚ƒ€ƒ„„„„„……†††…„„„„†‡‡…†…‡††‡ˆˆ……ƒƒ†…„‡‡„ƒ„†…„‰‰‹Žˆ‚„ˆˆ††……††…„„„…„ƒ„…„„…‡†„ƒ„ƒ€€‚„…„‡}|~~}€~}}~~}~}|zxxxvvustyxwwwz}€||}~~~|}}|{|~‚}w}ƒ‚…‡†…„„ƒƒ‚€‚„„ƒƒ„„†‡††††………†††…………………†……††……„„„…„††„ƒƒƒ‚ƒ……„„†‡†‡‡‡‡††…„„ƒƒ„……………†……„ƒ€€ƒ…ƒ„{|~}€€}~}~~~~}zzyzzxvvtsxxxwwy|~~~~~~}~}|~}}||}~~~w|ƒ„†‡……††……ƒ„ƒƒ…„„„…††…††…„„„……††††‡‡†……†‡ˆˆ‡‡‡‡††††ˆ‡††…„…†…†‡ˆ‡‡ˆ‡‡‡‡††‡†…ƒƒ„…†‡‡†…………ƒ€‚ƒƒƒ„‚€{|€~~~€€~~}~€{y{yyxwvvvvwxxuuv~€€~}}~||}|}}€~~€€}w}„†……ƒ„†„„ƒ„„‚„…„„„……„…………„„…†††‡ˆˆ‡‡‡†‡‡ˆˆ‡ˆˆ‡‡†ˆˆŠ‰‡†††‡‡†‡‡ˆˆ‡†††††……†…ƒƒƒƒ……†‡††……„‚€€…ƒ†y}€~~}€€~~~€~|zxxxxxwvxxvuussz~}}~~~~~|}~€€}}}|u}„„„„ƒƒ„†…„ƒƒƒ‚ƒ„……„„……„††††…„…†…†‡‡†„„„ƒƒ‚ƒ„„„……†††‡‡‡††‡………„…†‡†……††††…„„„ƒ„…‡†††……††…‚ƒ…ƒƒ……„€ƒƒxz~~}}|~~€{~}zyxxxvssuttwus{}~~}~~}|~~|}}|~€|w„…„…ƒ„„„ƒƒ…„ƒƒ‚‚ƒ„…††††‡†‡‡†‡‡†……†„ƒƒ„„ƒƒ‚ƒ‚ƒ…†…„…‡…„ƒƒƒ‚‚‚‚„……†…„„…†„……†††††††††…ƒ€ƒ†„„†…‚ƒ…zx}}€€}~~}~~~}€‚}{|{zywvurovutvwxy€~}|~~~€~}~}~|~}w……ƒ‚„„……ƒƒ„…ƒ‚‚ƒ‚ƒ„……†††‡‡††††…„„………ŠˆŠŠ‰‰‰ŠŠ†‚„†…„„†…ƒ‰‹Šˆ†ˆŠ†‚ƒ„„„ƒƒ‚…‡††††…†…††…ƒ€…„ƒ‚„…ƒ‚€…ƒzz|~~~~€}}~~€ƒ}x{|zyywttpvuvrswz~~}||||~€€}~~~~~w~„ƒƒƒ„„†…„†…„ƒ‚ƒ…†…ƒ„………††††††††††………‡…„‡‡‰‰‡†‡‹„‚„ƒ…„ƒ‰Œˆ†…††…†‰‹…‚„„„…„„††‡†…„……………„‚ƒ„„„„………‚‡‚z{}}€~}}}~}}€~‚}{z}|zyyvurnqtvttww|}~~~|}~}~~}~~€~x„„„……ƒ„…„…„„‚‚…‡†…„ƒ„„……„…†‡‡††††…††ˆ„ƒƒ„„…ˆŒ……ˆ‚ƒƒƒ‚ˆŠ†‡‰‰Š‹Œ‰…‡Œ†ƒƒ„††††…†…„…††„„„‚ƒ€‚ƒ„†…„…€y{€}€~|~}}~~€‚€}{zxvxuquuwvvvv||~~~~~~~€€~~~}w……„…‡…‚„††…„ƒƒ„…†…†„‚„………………†……††††‡ˆ…ƒ€‚ƒ‚†ˆ„‡ˆƒ…ƒ…†‰Šƒƒ„ƒ†‰„‡‹‡†……„…‡†…„…†††„…ƒ€‚ƒƒ‚ƒ…†„€„†€y{~~~~€~|€€€~||zyywtpwyyxwxz~~~€~~€€€€}}~~~}v€„ƒƒƒ……ƒƒ„…††††„‚‚ƒ…„ƒ„„…†……†…„††††‡‡„†€ƒ…†…„‡Š†Š„„…‡ˆ‰‚ƒ…†……ƒƒ…„ˆ‰††„ƒ…‡†„„…††……„‚‚ƒ„ƒƒ„„ƒ‚……y|}~~~~~€€}}~€~}}~~{zxzzvqovxyxxwuz~}~~~~}~~~~~z€‚‚ƒ„„„„ƒ„…††…„ƒ‚ƒƒ„„„„„………‡‡……†………ˆŠ†ˆƒ†‡††…„‰…‡ƒˆ†ƒŠ„ƒ†……„„…ƒ„ƒ…ˆ………………†„„…………„‚€‚‚„„ƒ‚ˆƒy{|}}}~}}~~}~}}}{|zyyxvryyxyzywz|}~~~~~~~~~}~~€z~ƒ„ƒ„…ƒ‚ƒ„†„„‚‚ƒ„„…†††„ƒ„„…†……††††…‡‹†ˆƒ…†………ƒ‡ƒ††€‰„†Š…………„„„ƒƒ„„…Š„…‡†††‡†…†…„„‚€ƒ„ƒ‚‚„„„„…„†‚€x{~~}~€€~~}}}}{y|yxu{zyyyz{{}~}~~~~€}}}~€€z€‚‚ƒƒ„†„„„„……†………†……„„††„‚„…†††††„‡‹†ˆ‚……„„„ƒ…€…ˆ‚‡„ˆ‡…‡‡……†…ƒƒƒ†‹„………‡†‡†‡…ƒƒ‚€ƒ……„„…†…‡…ƒ††ƒ~v}~|}}~}~~~€}{}}~}|zx|{xuyyz|zxxy}~}~}}~}~}{~~~~}{€‚‚‚ƒƒ††„„„„…†‡†………„ƒ………ƒ„„……††††„‡‡†‡‚„„„ƒƒƒ‡‚†ˆƒ‡„‡…‚‡†……†…„„€„ˆ‰…††‡‡‡‡††…„ƒƒ„„‚ƒ‚„……„„„‚‡†„{~~~~|~~~}}~~€|}}}}|z|zywwwwyzxxz~}}}~~~~~}€z|€ƒ„„ƒ†„‚‚ƒ…††„„†……„††ƒ„†…ƒ„„„……††…‰ˆ…ˆ‚„„„„ƒƒ†‡…†‡†ˆ‚…††………†‚ƒ†‹ˆ…††‡‡‡†…„„„‚‚„„‚„ƒ„…„…ƒ€…„„y€€€€~}}~~}‚|}}~}~€~{}zuuwyxxx{}|}}}|}~}|€~~~~~~~{ƒƒ…„„„……„„ƒƒ„„„…†‡†††…††‡†……ƒƒ„ƒ„…†…‹‰†ˆ‚„…„„„‡…„ˆ‚…‰†Šƒ‚…………„‚††Ž…„†††…†……„ƒƒ‚‚„…„ƒƒ„„„…‡…‚€„††ƒz€€~~~~}}~}~€‚~}}}}}}}~}}{xvxyyzyx{|~|€€~~~}€~|~}{ƒ„ƒƒƒƒ„…†…„ƒ„„„„„…………†……‡‡‡‡‡…ƒ„„………ƒ‹††‚€ƒƒƒƒ‡‡ˆ‰ƒƒ„‰††‡‚ƒ„„„€€…‡‹Šƒ…†……………„‚ƒ‚‚ƒ………ƒ„…„„…†„ƒ‚‡‡†ƒ{}€~}~~~||~€€~~~|zy|~}{yxxz{zz|}|}}~~~~}~€€}|}z}…ƒ‚‚‚ƒƒ„…†„‚ƒƒƒƒ…………†‡†…‡‡‡‡……†……………ƒŠ„ƒƒ„‚ƒ†‡„…Š……†…ˆŠ‚‡‡„„ƒƒƒ††‹ƒ„†‡‡‡†…„ƒ‚ƒƒƒ„††……„††††…ƒ…ˆ†…ƒ€~~}€€‚€}|~€€€~~}{yyz{xy}€}|{zxyyy}~|~€~~}}}||}}~z~…ƒ‚‚‚ƒƒ„††…ƒ‚„…………††…†††„ƒ‚…‡‡…………ƒ‰…„…‡†…†††‹‹„…†††…ˆˆ„…‡†………†Š‡„†‡‡‡†‡„‚‚ƒ…„„……††……………„ƒ†„„‚€}~~€€€~||}€€~||{zy{}zz{~yzzxvyxx{}|~|}~}~}}}}zz…„ƒƒ‚‚‚ƒ…††…ƒ‚ƒƒ……„„„……†…„……†‡†„„††„Љˆ‡‡‡‡‡ˆŠˆ…„†‡‡†…ƒ‡Š‡„ƒƒ„…Žˆ†‡‡‡††……„‚ƒ‚„„ƒƒ‚ƒ†…††……†ƒ‚…„„‚€‚€€~}€}~~~}~}~~~}||||}|{~}|{yx}}yz||}}~~~~|}{{ƒ‡‡ƒ‚‚ƒ„„††„ƒƒƒƒ„……„ƒ„………†ˆ‰‡ˆ‡…„†‡††‡‡†‡ˆˆ‡‡†……‡‡‡‡‡††………ˆŠŠ‹Œ‹Š‡†‡‡†††…„ƒƒ‚„„‚ƒ„……„………†‡†…„†…„„„‚ƒ}}|~~~~‚„‚€~||~{z€{||{{yz|}zy~}~€~~€~~€y|„…†ƒƒƒƒ„„†……„‚‚ƒƒ‚‚‚„…†‰‰‰‰‡‡††‡†††††‡‡…ƒ„„…†‡†………††††…„„………ƒ„†††††††„ƒƒ‚€‚…†††„„…………„€„ˆ…„ƒ‚„„}~~€‚}}€‚ƒ€€~|}||||{{{~|}{{zyyzzzz~}~~}|~|~~||}y~‚„…‚‚„ƒƒ„„„…††…‚‚‚‚…‡‚„………‡‡ˆˆ‡ˆˆˆ‡††‡‰Šˆ‡‡††‡‡†…„…††……†††…††††‡†††…„„ƒ‚~}€ƒ…ƒƒ……„………‚†‡†„ƒ‚„ƒ~‚~~~}~€€~~€~||}}zy{||{{|zyzxxz{yz|~~}}~}~~}~~}}€|z€€…†‚‚ƒ„„„„…††………ƒƒ‚‚ƒ‰††…„„„ƒ‚ƒ‡‡ˆˆˆ‡‡‡ˆˆ‡‡†††………†…††‡†††‡†††ˆ‡††††„‚‚‚~~‚‚ƒ…„„„ƒ…†…„„„……†……ƒ…„ƒ€}~~~}~~}~€~}}~||}|{z||z|}}|}~{}}~{}~}€}~€~z|„ƒ„†‚‚ƒ„……„†‡‡†‡…ƒ‚„ƒ„ˆ€€‚ƒ„‚„‰††„‚…†‡†††‡‡††…„……………†††‡‡‡††‡††…„ƒ„……„‚‚ƒ|…………„Œ‰……‡‡…„„„„‚†††…„„„‚€}€~}~~~~~~€~~~}|z|}}}z||~~~}{|}}€~~~}~‚}}x}‚„„†‚‚ƒƒ„„ƒ„‡‡†‡„‚‚ƒ†‡~}‚ƒ„‹‡†ˆ…‚…†‡‡†…ƒƒ‚‚††…†‡††‡‡††‡‡…ƒ€~€‚‚„„€ƒˆ‰ƒ~†ˆˆ‡‰‹Ž…ƒ…††…„„ƒ…‡…„…„‚ƒ‚€|€~}}~~~}}~~~~}}}}~~~|{}}}~€}}}€~~~~~}}{€…ƒ‚„‚„„„„„…†…†‡…‚ƒ‚ƒˆ…~~|ƒƒŠˆƒ…„„…†††‡„‚‡‡‡„„ƒ„††††††…ƒ€‚„„ˆ‡‡‰‹‰„‚€…††‰……††‰ˆ„„ƒ……„„…‚~‚††…ƒ‚‚‚……ƒƒ{€}~€‚€}|€~~€||}}~}}€}~|}|~~}~}~}{z„ƒ††‚‚ƒ„ƒƒƒ„†††††………„…ƒ‚‚~‡Šƒƒ†‚„…††††ƒŠ††ƒ‚…†‡‡††‡„€…†‘‘ŒŠ„Š‹€…{‰†…†…„„ƒ„„…††…„„‚‚„††…„ƒ‚…„‚€€}~€~}~}}}~~~}}}}||}|}}|||~~~}~}||~~}€}|~xz‚ƒ„††ƒƒƒƒ„„„„†‡†††‡†„„†ƒ‡‚€„‰„‚…ƒ………„„ŠŠƒ‚„†‚‡‡‡†‡†‚ˆ‹‹ˆ†……Љ€‚†|†‰‡‡††…„ƒƒ„†…„……„‚ƒ…††…„„‚ƒ„‚€€‚~|}}~~€€€€||~{|}|{|}{€~~~{}~}|~}€~€~}~|‚ƒƒ„…ƒ‚ƒƒ„„ƒƒ„…†††‡…„ƒ……}†„}ƒ†„…€ƒƒ…†…‚ˆ‰‡ŒŠ„ˆ…†††‡„‚ŠŒŒ„‚„„„„„ŠŠ‚‚†‡€‰†…†……„ƒƒ„………††…ƒƒ…‡…„ƒ„„ƒ††„‚ƒ~€~~€€€€~~}{}~~~~€}~|~~}€€}}~~~}||}‚„„„„†ƒ‚ƒƒ„ƒƒ„„…†††‡††‡‡††‚ƒ„…„ƒƒ„†ƒ†‹…І‹†††„††‡†ƒ‹‰‰ƒ„„…†…„ƒƒƒƒˆ‡€Š‡†…ƒƒƒƒƒ„„……………ƒ…‡†„ƒƒ„ƒ„…„„„ƒ‚~~€~€€~{~~€€~~||€€~~~€‚€€~~|}~~~{|z}„„ƒƒ…ƒ‚ƒƒ„„…„…†„„„„…†‡‡ˆ€|‡…€„„‰ƒ€„„„„ƒ‰…ˆ…€ˆˆ‚‰ƒ„††„†‡†Š‚„……†„ƒ„‚€‚‚„‰„|‰‰……‚ƒ…„ƒ†‡†……„ƒ‚„††„„……‚€„…ƒ‚ƒƒ‚}‚€~~}€|€€€~}~~~~~€‚€}}ƒ~€~€~}~}zz€„…„„††„„ƒ„„„ƒ„…„„ƒƒ„‡‡ˆ‹„~…‡„…ˆ‹ƒ„„ƒ‚ƒˆ†ƒŠ…ˆˆ…ƒ„‚‡…„ˆ‚„…„ƒƒ€€ƒ‚ƒ‡…~ƒˆ‡‚„‚ƒ…‚‚ƒƒ„„…‚„‡†„„„„‚€„„‚ƒƒ„ƒ~‚ƒ~‚€€€€€€€||}}|z~‚€~~}}}~~~~~}}€‚ƒƒƒ„…ƒƒ„„„„ƒƒ…††„„…††‡‰„…‡‡†ƒ…‡‚ƒ„†„…‡ƒƒƒ€‚†„…†‚ƒ†ƒ…†ƒƒ„„ƒ…ˆ…„‚ƒˆ‡Š€„…ˆ€€ƒ„‚|‰Œ…„…„€ƒ„…†„…†„€„ƒ„…ƒƒ‚‚~~€€€€~~~~}|{||}‚ƒ€€€€€„…€}~~}}~|}‚ƒƒƒ„„…ƒ‚ƒ„„…†…††……†……„„ˆ‡‚ƒ‡Œ‰‚…ˆ‚„†„†…€€|~€€…‡‚ƒ‚‰„ƒ†‚ƒ††ƒ…‰†ƒ€ˆ€€†‰‚†ˆ†‰€ƒ’Œ„„ƒ‚„………„†‡„€„†„„„ƒ‚€~~‚€}}|{|}~||{{~€ƒ‚€ƒ‚†|~~~}|~€ƒƒƒ„„„†„‚„„„………………†††…ƒƒŠ‹ƒ„‡‰ˆ†‡‰‚€ƒ„ƒ…„€€‚ƒ„‚…„ˆ‡€…ƒ††„†‹††„Š€‡Š„‚‰ˆ††“ƒ……‚„…††……‡†ƒƒ†‡††…„„ƒ€~‚ƒ~}|||~€€~~~~~‚‚€‚‚…~~~€~|€‚€„ƒƒ„„ƒƒ……ƒ‚‚ƒ…………†††……„ƒ‡Š„…‡………‡‰„ƒ…†ˆ†‚ˆ‡‡ˆ‡†……ƒ„‚„…†‰‚ƒƒ…††‡‡„‡‡Š…‚ˆ‰‚‚ƒ‹…‚€‚”…ƒ…ƒ‚ƒ†††………†„‚ƒ††„„„‚ƒ„ƒ€€€~€~~}~€€€}|}~}~€ƒ‚€€~…ƒ}}|}}‚€„„ƒƒƒ„ƒ„…‚‚‚ƒ„„………††„„„„…„ƒ…………ƒ†‰„‚ƒ„†ƒ†ˆ‡Š‹Š‰ŠŠˆƒ‚‚††‰‹ƒƒƒ‚€€€ƒƒ†‰…„ˆˆƒ…ƒŒ‹ˆ‰Ž†…‡…‚„†……………††‚€‚‡†„…‡…‚‚ƒ‚€€‚€~~~~€~}}~~{|~‚€€€€„ƒ{|~‚€‚ƒƒ„‚ƒ„„ƒ†‡ƒ‚ƒƒƒƒ…††††…„……††††…†‡…†Œ‰‡…‰ŠƒŠ†††‡‡†……‡†„‡‡Œ‹ƒƒƒ}~€ƒ‰„„†‹…„ƒ‚„ˆŠ‹‰„„†…‚„……‡†…………‚ƒ„…„………ƒ‚‚…ƒ€~~|~€€}~~{{~~€€~}€€ƒ‚€€‚€…}{}ƒ……ƒ‚„ƒ„„…‡„‚‚ƒ„„……†‡…„………†‡††…………†ˆ‰ˆ†ŠŽŽŒˆ††‡‡‡‡‡†Š‹ŠŠ„†‡‡‰Œ…ƒ€‚ƒ‹€„ƒƒ……ƒ‚„††…†…‚ƒ…‡‡††……‚……†††…„„ƒ‚‚€ƒ…ƒƒ‚~€ƒ€€€€€}}€ƒ~}|~‚‚€€‚~}~}€…„‚‚‚ƒ……‡‡„‚‚ƒ„……„„…‡††……„„†„„…„„…†††……„…†‡‡‡‡‡†…†††‡ŠŒ‡†††…†Œ‹††‡‰‹ˆƒ‚ƒ†‹‡ƒ„„…„…‡ˆ†…‡„‚…„…………†††‚††…„„„ƒƒƒƒ„ƒ€‚‚‚~€€~‚ƒ~}}}~‚€€ƒ€~‚„ƒ‚……‚‚„„…‡†„‚ƒ„ƒ„ƒƒ„…†‡‡……†‡…„…„„…†††††††………†…„„†‡‡‡††††††††‡††ˆ‹‹Œ‹‰„‚…†…ƒ‚ƒ„………†……††…€……†……„„…†„‚ƒ†‡†…„ƒƒƒƒ„†ƒ‚‚‚‚€~||}~~}~|y„ƒ‚€€~ƒ‚‚~|€„…ƒƒƒƒ„…„„‡‡†ƒ‚ƒƒ‚‚‚ƒ…††……………„„„„…††……†‡†…„„ƒ„„…†‡††‡‡‡ˆˆ‡‡†††††‡†…ƒƒƒƒ„…„„ƒ……„„…†‡………„€„…†‡†„‚ƒ…†ƒ‚„††……„„ƒ‚‚ƒ„ƒƒ‚‚ƒ|{|‚~zz}{|}{€€ƒ‚€€€|~€„€~~…„ƒƒ„„„ƒƒˆˆ†ƒ€ƒ…ƒ‚‚„…†…„ƒ„……„„…†…„„„………„„ƒ„†‡‡‡‡††ˆ‡†††……„ƒƒ„„…†…ƒƒ„„…†…„ƒ…††…†‡†††…‚€ƒ„…†…„………„ƒ‚…‡†„„„„ƒ‚‚ƒ…„‚„€{{|~€€~||€|{z|€„€}€‚‚€€€„€€„†„„‚‚ƒ„„ƒƒ†‡ˆ‡„ƒ‚ƒ……ƒ„„…………„„„„ƒƒ„…„……†††„„……„ƒ…‡‡††††‡†„„„…„ƒ‚ƒƒƒ‚‚‚„………„„„„†‡†††††††…ƒƒ„ƒ…‡†…†‡‡…„ƒ‚…†„‚ƒƒƒ„„…„…†„ƒ€ƒ„€€}{~~}|~~}}~{|€€‚€‚€~€€€…ƒ€‚…‡…ƒ„„ƒ‚…†‡††…‚„…„„„…………††††„ƒ„„„„„„„……„„…………††††††‡†……„„…………„ƒƒƒ…††……„„†‡‡††††††‡†€„„„†‡†…†††…„€……ƒ„…ƒ„††…………„ƒ…€~€}~~{z~}{{~~~ƒ€~€€€~~~†ƒ€ƒ„„‚‚ƒ„…„ƒ‚ƒ…‡†‡†‚ƒ„ƒ‚ƒƒ„„…‡‡‡‡‡†‡†…………†…„„„…‡†‡‡‡‡‡†………†„‚„„‚‚ƒƒƒƒ…‡†……†……‡††…‡‡‡††„‚‚ƒ„…‡‡…„††…‡†ƒƒ‰‡„„…„ƒ„…„„†‡…ƒ‚€„€€€~~~~|~‚€€|ŒŒŒ‹‹‰‰‰ˆ‰‰‡‡‡ˆ‰ˆ‡ˆˆˆ‡‡ˆ‰‰Š‰‡‡ˆ‡ˆ‰ŠŠ‰‰‰‰ˆ‡ˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡‡†††††††††††…†…„ƒƒ‡‰†††ˆ‰‰ˆ‡‡†‚‚„††……………†………†††‡‡……‡‡‡†‡‡‡‡‡‡‡ˆˆˆˆ‡ˆˆ‡‡ˆ‰Š‹Šˆˆˆˆ‹Šˆˆˆ‡Š‰‡‡ˆ‰‹ˆ‡‰Š‰‹‰Ž’ŒŒŒŒŒŠ‰‰ˆ‰Š‰ˆˆ‡‡‡‡‡‰‰ˆ‡‰‰‰ˆˆ‰‰‰‰ˆ‰ŠˆˆŠŠ‰‰‰‰‡‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡††………†……†††…………ƒ‡Š‰‰ŠŠ‰‰‰Š‰‡Š…€„………„„„…†††…††‡‡†…††††††‡‡‡‡‡‡‡‡‡‡‡‡‡‰‰ˆ‰‰ˆˆ‡‡ˆˆ‰ˆ‡ˆ‡ˆ‰‰‡ˆ‰ŠŠ‡‡ˆˆ‰‹ˆ‰“ŽŽŒŒ‹‹‹‹ŠˆˆŠ‹ŠŠŠˆˆˆ‡ˆ‡‡‡‰‰Šˆ‡‰ŠŠ‰‰‰‰ˆˆ‰‰‰ˆˆˆˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡…………†††………††…„‚…†………„„„„„„‚„„€ƒƒ„„„„„……†††††……†††††‡‡‡‡‡‡‡‡‡‡‡‡ˆ‡‡ˆ‰ˆ‰‰‰‰ˆ‡ˆ‰‰ˆ‡ˆ‡‰‰‡†ˆŠ‹Š‡‡‡‰Š‰ˆ‰Œ‹Œ‹ŠŠŠ‰ˆ‡ˆŠ‹Šˆ‡ˆ‹Šˆ‡‡‡ˆˆˆˆˆˆ‰‰‰‰Š‰ˆ‰‰‰ˆ‡‡ˆ‡‡‡‡‡‡‡‡‡‡‡‡‡††††………††††…††…„„ƒ„†††…„„„…„†…ƒƒƒ‡‚‚„…„…††……………„…†‡††‡†††‡‡†††‡‡‡‡‡†‡‡ˆ‰ŠŠŠ‰ˆ‡ˆ‰ˆˆ‰‰ˆ‰ˆ‡ˆ‰ŠŠ‰ˆˆ‡ˆŠ‰ˆˆ‹ŠŠŠ‹‹ŠŠ‰ˆ‰‰ˆˆ‰ˆ‡‡‡‰‰‡†‡‡ˆˆ‡‡ˆˆˆ‰‰ˆ‰‰ˆˆ‡‡‡‡ˆ‡‡†‡‡‡‡‡†††‡††††††††……†††…†…„„……„………„„„„„†ˆ‡†ƒ„‹‡‚‚‚‚„…†††††………††…††…†‡‡‡‡‡‡†‡‡†‡‡ˆˆ‰Šˆ‡‡ˆˆˆˆˆˆ‰‰Šˆ‰ˆ‡ˆ‰‰Šˆ‡ˆ‡ˆŠ‰ˆ‰‹ˆ‰‰‰Š‹ŠŠ‰‰‰ˆ‡‡‡‡‡‡ˆ‰ˆ‡‡‡ˆ‡†‡ˆˆˆ‰Š‰ˆˆˆˆ‡‡ˆ‰ˆˆ‡†‡‡‡‡‡†…†††…††…††‡†„„„…………„„ƒ„…„……„ƒ‚…†††„‚„†‡Š‰‚„………†††…†††‡……†††‡‡‡‡††‡‡‡‡ˆˆ‡‰ŠŠˆ‡ˆˆˆˆ‡‡‡ˆ‰Š‰ˆˆˆ‡ˆ‹‰ˆˆˆˆ‰ŠŠ‰‹‹‹‹Š‰‹Œ‰‰Š‰Š‰‡‡‡‡ˆˆ‡‡ˆˆ‡‡‡‡‡‡ˆˆˆ‰‰‡‡‡ˆˆ‡‡ˆˆˆˆˆˆ‡†††‡‡‡‡‡†‡††……………„„„………„„„‚‚………„ƒ€}~€‚ƒƒ…ƒ‚„ƒƒ†„ƒ„„„„………„„……………†††‡†††††††††‡ˆ‡ˆˆ‡‡‡‡ˆˆ‡‡‡‡‡ˆˆ‰‡†ˆ‰Šˆ‡‡‡ˆŠ‰ˆŠŠŠŒ‹‹ŠŠŠˆˆ‰‰‰‰ˆ‰ˆ‡‡ˆ‰‡‡‡‡‡ˆ‡‡‡ˆˆˆˆˆ‡‡‡‡‡ˆˆˆ‡‡‡ˆˆ‡††‡‡††††…†………„„„„„„„„„„„„„‚†…„„~~‚ƒƒ„†„ƒƒƒƒ‚‚……†………„„„„„……………………††††‡†…††‡ˆˆˆ‡‡†ˆˆ‡ˆ†‡‡‡‡ˆ‡‡‡†ˆ‰Š‹ˆ‡†‡‡Š‰‰‰ŠŠŒ‹Š‰‰‰ˆ‰Š‰‰‰ˆˆˆˆ‡‡‰ˆ††††‡ˆˆ‡‡‡‡ˆˆˆˆ‡‡‡‡‡‡‡‡‡‡‡‡†††………„…………„„„„„„…„„„ƒƒƒƒ„‚€†„ƒ„„€€ƒ‚‚ƒ„ƒ„ˆˆ„ƒƒ„„………………„„„„††„„…………„…†‡‡††††‡‡‡‡‡††‡ˆ‡‡ˆ‡‡†‡ˆ†‡ˆˆ‰Š‰ˆ‡‡‡ˆˆˆ‰‰‰ŠŽŒ‹Š‰‰‰‰‰‰‰ˆ‡‡‡‡‡††‡…………†‡‡‡‡‡‡ˆ‰‡†‡†‡‡‡‡‡‡‡‡‡†††††………………„„„„„„„ƒƒƒ„„„„„„‚‚ƒƒƒ††‚……ƒ‚‚€ƒ…‡…ƒƒƒ„„ƒƒ„„„ƒƒ„„……„„…………………†††…†‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡ˆŠŠ‡†‡ˆˆˆ‡‡‡ˆ‰‰Š‰‹ŠŠ‰ˆˆˆˆ‡ˆ‰‡††‡‡†…†……†‡‡‡‡‡‡‡‡‡††‡‡‡‡‡‡‡†‡‡‡‡‡‡‡†…„„„ƒ„……„„„„ƒƒƒ„„„ƒƒ„ƒ‚ƒƒƒ„†„†…‚‚ƒƒ€€ƒƒƒƒ‚„„„„„ƒ„ƒƒƒ„„„„„„„…………„„„………†‡‡‡‡‡‡‡†‡‡‡‡‡†ˆˆˆ‡‡‡‡‡‡Šˆ††ˆˆˆˆˆˆˆˆŠŠŒŠ‹‹ŠŠŠˆ‰Šˆˆˆˆ‡‡‡ˆ‡‡‡‡ˆ‡†‡††‡‡‡‡‡ˆ‡‡‡‡‡‡‡‡‡††‡††…„………„ƒƒƒƒ„…„ƒƒƒ„„„„„ƒ‚ƒƒƒƒƒƒƒƒ…ƒ‚‚‚„‚€‚‚ƒƒƒ„ƒƒƒƒƒƒƒƒ„„ƒƒ„„„„……„„…………††‡†‡‡‡‡†††‡‡‡‡‡†‡‡‡‡‡ˆ‰ˆˆ‰‡††‡ˆ‰‰ˆˆˆ‰‰Š‹Œ‹Š‰‰‰Š‰ˆ‡‡‡‡ˆ‰ˆ†‡‡‡†‡‡‡‡‡‡‡‡‡‡ˆ††‡††††††‡‡†………………„„ƒƒƒƒ„„„ƒƒƒ„„ƒƒƒƒƒ‚‚‚„‚‚‚‚‚ƒƒ‚‚‚…ƒ€‚‚‚ƒ„ƒ‚ƒƒƒƒƒ„„ƒƒƒƒ„„„„„„„„………†‡‡‡‡‡‡†…†‡††‡‡‡‡‡‡‡‡‡…‡‡ˆ‹Š‡…†‡‡‰‰‰‰ˆˆˆˆ‹Œ‹Šˆˆ‡ˆ‡††‡‡‡‡ˆ‡††‡†…†‡‡‡‡‡‡‡‡‡‡†††‡‡‡†††††††††……†…„„ƒ„„„„ƒ‚ƒƒ‚‚‚ƒƒƒƒƒƒƒ‚‚‚„ƒƒ„ƒ„ƒ‚ƒƒƒƒ„„„ƒƒƒƒƒ„„„ƒƒƒ„………††††††‡†……†‡†‡‡††‡‡‡‡‡†††‰‰‡‡‡‡‡‡‡‡‡ˆ‡ˆ‰‰ŠŠ‹Šˆˆ‡‡‡ˆˆˆˆ‡‡‡††…‡‡†‡‡‡‡‡‡††‡‡‡‡††‡‡‡†††††††…††……„ƒƒ„„…„ƒ‚‚ƒƒ„ƒ‚ƒƒƒƒƒ‚€ƒ„…„‚‚‚‚‚€‚ƒƒ‚‚ƒ„ƒ‚„ƒƒ‚‚ƒƒƒ„„„ƒƒƒƒƒ„…††††…†‡‡‡‡‡‡‡‡†††‡‡‡‡‡†††ˆˆ……†‡‡ˆ‰ˆ‡‡ˆ‰Š‰‹ŠŠŒ‹ŠŠŠ‰‰‰ˆˆ‡ˆˆ‡‡‡‡‡‡‡‡†††……†‡‡‡‡†‡‡‡‡‡‡†††………††††…„ƒƒ„ƒ„ƒƒ‚ƒƒ‚‚‚‚‚‚ƒƒ‚‚ƒ…„‚‚‚€‚‚‚‚ƒ‚‚‚‚‚ƒƒƒƒƒ‚ƒ„ƒƒ„„„„„„„„„…†‡†††††††††………………††‡‡‡†‡ˆ‡††…†‡‡ˆˆˆ‡ˆ‰ŠŠˆ‰Š‹Š‹‹ŠŠ‰‰Šˆˆˆ‰ˆ‡‡‡‡†…†…†‡†…†‡‡‡‡‡†‡‡‡‡†††……………††…„„„„„„ƒ‚‚‚‚‚‚‚‚ƒƒ‚‚‚‚~€€~~~~~€‚‚‚‚‚‚‚‚‚ƒƒ„„„„„„„ƒ„„„………††††‡……††…„„…†††……†‡‡‡††ˆ†…„…‡‡ˆˆ‰ˆ‡ˆ‰Š‹ˆŠŠŠŠ‹‰‰‰ŠŠŠŠ‰‰ˆˆ‡‡‡††‡‡††‡‡†…†‡‡‡‡‡‡†‡††…„„„„……†…„„„„…„ƒƒƒ‚‚‚‚‚‚‚€‚…††…‚~~€‚ƒ‚ƒ…„€‚‚‚‚ƒ„ƒ„„„„„……„…………„„……„„…††„„…††††††‡ˆ‡††ˆ†„„†‡‡‡ˆˆ‡‡ˆ‰‰‰ˆ‹Š‰Š‹ŠŠ‰Š‹Š‹Š‰‰‰ˆ‡‡†…†‡†‰ˆ‡†…†‡‡‡‡‡††††……„„„„„„ƒ„„„„„„ƒƒƒ‚‚‚‚‚‚‚‚‚€€„…†ˆˆ‡†ƒ„„…†…ƒ€€‚‚‚‚‚ƒ‚‚ƒƒƒ„„„„…………„„„…†††………†††††††‡‡‡‡††‡‡……††‡‡‡††‡‰‰‰‰ˆŒŒ‹Š‰‰Š‰‰Š‹‹Šˆˆˆˆ†††……†…†‡‡††‡†††………„„„„„„„„„„„„„ƒƒƒƒƒƒƒ‚‚‚‚‚‚€€€€€€€€€‚„‡‡„€~}}~‚‚‚‚‚‚‚‚‚ƒƒ‚‚ƒ„ƒƒƒƒƒƒ„„„„„„………„…………†††…‡‡‡‡ˆ‡†††…†‡‡‡‡†‡‡ˆˆ‡ˆ‰‡‹‰ˆ‡ˆ‰‰Š‰ˆŠ‹‹Š‰‰ˆ‡†………†‡‡‡‡†††……„ƒ„„„„„„ƒ„„„„„„„…„ƒ‚‚ƒƒƒ‚‚‚‚€€€€€€€€€€€€€€‚ƒ†‰ˆ…ƒ„…‚€€‚‚‚ƒƒƒƒ„„ƒƒƒ„„„„„„„ƒ„†…†…„…††‡…†‡‡‡‡ˆˆ‡……†††‡ˆ‡‡‡ˆˆˆˆ‰ˆ†‰‡‡ˆˆˆ‰‰‰‰‰‰ˆ‰ˆ‡‡‡†„ƒ‚…††‡‡…„„„……ƒƒƒƒƒ„„„……„„ƒƒ„„ƒ‚‚‚ƒ‚‚‚‚‚‚€‚‚€€€€€€€‚„†‡‚€€€€€‚‚‚ƒ‚‚ƒƒƒƒ„„„„„„„…††………†††††††ˆŠˆ†…†‡‡‡‡‡‡‡ˆˆˆˆ‰Šˆ‡‡†‡‰‰‰ŠŠŠ‰‰ŠŠŠ‰‡††……„‚‚…‡ˆ‡…„„„„…„„ƒƒ‚ƒƒƒ„„„ƒƒ„…„ƒ‚‚‚‚‚‚‚€€‚‚€€€€€€€€€€€€€€€€€€‚‚‚‚ƒƒƒƒ„„„……„ƒ„…………††††‡‡†‡ˆˆ†„…†‡‡‡‡‡‡‡‡ˆˆˆˆˆ‡‡ˆˆ‡‰ŒŠ‰Š‹ŠŠŠŠ‰‡‡ˆ‡†…„‚ƒ†‡‡†…………„„ƒƒ„ƒ‚‚ƒ‚ƒƒ„ƒƒƒƒ„ƒ‚‚‚‚‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ƒƒƒ‚‚ƒƒƒƒƒ„„ƒƒ„„…„„„„…††‡‡…‡ˆ†„„‡‡††‡‡‡ˆˆ‡‡‡ˆ‡†‡ˆˆˆˆˆ‹‹Š‹Œ‹‰‰‰ˆ‡‡‡ˆ†„ƒ‚‚†ˆ‡†„…………ƒƒ„„ƒƒƒƒ‚‚‚ƒƒ‚‚‚ƒ‚‚‚ƒ‚‚€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€~€€€€‚‚‚€‚ƒƒƒƒ„„ƒƒ„…………„„„†……††‡‡…ƒ†‡‡††‡‡‡‡‡‡‡ˆˆ‡‡‡ˆˆ‰‰‰‰Š‹‹Œ‹‰‰ˆ‡‡‡‡‡‡†„‚ƒ‡‡†„„„„ƒ‚‚ƒ„„ƒƒƒ‚‚‚‚‚‚ƒƒ‚‚‚ƒƒ‚|}€}}}€€€€€€€€€€€~~~€€€€€€€€‚ƒƒƒƒƒ„„„„…††…„…†††††‡ˆ„…††††††‡‡‡‡‡‡‡‡‡‡‡‡ˆˆˆˆ‰‹‹‹‹ŠŠ‰‰ˆ‡‡†‡‡‡‡†††ˆ‡…„ƒƒƒƒƒ‚ƒ„„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚‚‚~€ƒ‚€€€~|{|€€|{|{{|}|}}~~||}||{{z{|€||}|{||~€€‚‚ƒƒ„„„…„„„…„„„………††‡‡„……………†††††‡ˆˆˆˆ‡‡‡‡‰ˆˆˆ‰Š‹‰ˆ‰ˆ‡ˆˆ‡‡††††………†‡†……„ƒƒƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€ƒ€~}††}~~}}}|{||}€€€~€€~~€‚}~€}{}€€‚‚‚ƒƒƒƒƒ„ƒƒƒ„„„……………††…………†……†††††††‡‡ˆ‡‡ˆˆŠ‰‹Šˆ‰‹‰ˆ‰Š‡‡‡‡‡‡††‡†ƒ„……†…„„„„ƒƒ„„ƒ‚‚‚‚‚€€€‚}{~€ƒ‡…}€ƒ‚‚€€~‚„„„„„‚~€„€€€‚……‚€|}€€€€‚‚‚‚‚ƒ„„ƒƒ„„„…„………………†††††††…„„††…††‡ˆˆ‡‡‡ˆ‘ŒŠ‰‰‹Š‰Š‰‡‡‡†…†‡††…‚ƒˆ†„„……„„„„ƒƒƒƒƒ€€‚€€}}|~€„…‚€~………‡†‡„€€€}}‚ƒ‚ƒ„ƒ€ƒ€~}‚„‡ˆ‚€{~€€€€€‚‚‚‚ƒƒƒ„„ƒ„„……†††………„………†‡‡†…†††‡‡ˆ‰ˆ‡‡†‡•‘‹‰‰‰‰‰‰‡‡‡‡†„…‡††„€€€„Ї………„ƒƒƒƒƒ„ƒƒƒ€€€€€€€|~|{€…„€€€€€€€€€€€€€~}~€€€|z~€€‡…€}|€€€€‚‚‚‚‚ƒ„„ƒƒƒƒ„…†††††…†‡‡‡‡‡‡‡†…††††‡ˆ‰ˆˆ‡ˆ”’‘ŽŠ‰Š‰‰‰ˆˆ‡‡†„ƒƒ‚€€€€€…Ї…„„„„„„„„„ƒ‚‚‚€€€€€€€€€|}~~~„ƒ€€€€~~~}~~€€€~}|~~~€‚€|y~€€‚„~}€€€‚‚‚‚ƒ„ƒƒ‚ƒƒƒ„…†………†††‡‡‡††ˆ‡‡†††‡ˆ‡‡ˆˆ‡‡‡“ŽŠˆ‹‰ˆ‰‰‡††††„€€€€€…ˆ†††„‚ƒ„„„ƒƒ‚‚‚‚€€€€€€€€€{|~€~~~€~|}~}|{{{||~~||{{zy{|{~‚€|{~€€€~€€€€€€€‚‚ƒƒ„„„ƒƒ‚ƒƒ„…††††‡‡‡ˆˆ†††††‡‡‡‡‰Šˆ‡ˆˆˆˆˆ‘Œ‰‹ˆ‡ˆ‡†…†…„~€…ˆ†††…ƒ‚ƒ‚‚‚ƒ‚‚ƒƒ‚€€€€€€€€€€€~}~ƒ}~~~~~~€€€~}}|{~€~~}}}}~z~‚‚||€€€€€€‚ƒ„ƒ‚ƒ‚ƒ„„„„………†‡‡‡‡‡‡†††……‡‡‡‡ˆ‰ˆ‡‡ˆ‰‰‹‘’’‘ŽŠŠ‰ˆ‰ˆ‡†…‚‚€€€€€ƒˆ‡…„ƒƒƒƒ‚‚‚‚‚ƒƒ‚‚€€€€€€€€~~~ƒ€~}~~~}…†……„ƒ}}€‚‚‚‚‚€€}}||}€‚€€€€€ƒƒ‚„ƒƒƒ„„„………†‡‡ˆˆ‡‡†……††‡‡ˆˆˆ‡‡‡‡ˆ‡Š‘‘’‘ŽŒŒ‰ˆ‡‡†…ƒ€€€€€€ƒˆˆ†……ƒƒ„ƒ‚€€‚‚€€‚€€€€€€€€~}~€‚}~~~~~}€‚‚‚~~ƒ„„„„€€€€‚‚‚‚‚ƒƒƒƒƒ„„…‡†ˆˆˆ‡†„„„…‡‡‰‰ˆˆ‡‡‡‡‡ˆ‹Œ‘‘‰‡…ƒ€€€€€€€‚ƒˆˆ†††„„ƒ‚€€€€€€€€€€€€€€~}}€~~}~~}}~~}|}~~~€‚~‚„„‚€€€€€‚‚‚‚‚‚ƒƒƒƒ…„…†‡‡ˆˆˆ‡†„……†‡‡ˆˆ‡‡‡††ˆ‰ŠŒ’‘‰„€€€€€€€€€€€†ˆ†……„„ƒ‚€‚ƒ‚‚‚‚€‚‚€€€€€€€€}~€€€~~~~||||~~€€~}}~~~~€€€‚~|‚€€€€€€€€€‚‚ƒƒ‚‚‚ƒƒƒƒ…„„…†‡‡‡ˆ‡ˆ†‡‡†…†‡‡ˆ‰‹Š‰ˆ‡††ˆŠŒŒŒŠ‡ƒ‚€€€€€€€€€„ˆ…ƒ„„ƒƒƒƒ‚‚‚‚ƒƒƒ‚€€€€€~~€~€~~}€~~~}{{{{||}~€~~||}~~~~~ƒ{|€€€€€€€€€‚ƒ„„…ƒ‚ƒ„……„„…„„…††‡‡†‡Šˆ†……„…‡‡‡ˆŠŠ‰ˆˆˆ‡ˆŠŒ‹‘‘Šˆ†‚€€€€€€€€€€€€„‰„ƒƒƒ‚ƒ‚‚‚€€‚ƒ‚‚€€~€€‚‚~~€€}~~~~~~}}}}|{z}€€~}{{||{zz}‚}|}€€€€€€€‚ƒ„„„„ƒ‚ƒ„„„…„„……††‡‡‡†ˆ‡…„ƒ„†‡‡‡ˆ‰ˆˆˆ‡‡ˆŠ‹‹‰ˆŽˆ†…„€€~€€€€€€€€€„ˆ…„ƒƒ‚‚‚‚‚‚‚€€€~€€€€€‚€€€€€€~~€€~}z}‚~€€€€€€€‚ƒ…„‚‚‚ƒ„„………†‡‡‡‡‡†‡ˆ†…………†‡‡‡ˆˆ‡‡‡ˆˆŠŒŒŠˆ‡‹‰‡…„………€€€€€€€€€€€€‚‡†„ƒƒƒ‚ƒ‚€€€~}€€€~€€€€€€€‚ƒƒ‚‚‚‚€€€‚‚‚‚~}€ƒ‚€€€€€‚ƒ„„„ƒƒƒƒ„„„………†‡‡‡‡‡‡‡†„„†‡‡‡‡‡‡††††‡‡ˆŠ‰‡‡‡ˆ……†…„„„ƒ€€€€€€€€€€€€‚ˆ…‚‚‚‚‚‚€‚€€€€~~~~~€€€€€€€€€€€€€€€~~}}~€€ƒ‚‚‚ƒ„………ƒƒ„……„„†‡‡‡‡‡†……„…‡‡ˆˆ††……†‡‡‰Š‹ˆ†‡‡ƒ…†††…„ƒ€€€€€€€€€€€€‡‡ƒ‚ƒ€€€€€€~~€€€~€€~~~~~~~~~~~~~~~~~}}}}~~~~~~~}~~€€€‚ƒ€€‚ƒƒ„…„……‡†……††‡‡‡‡…†††‡ˆˆˆ†††…†‡ˆ‰‰‰ˆˆˆ‡ƒ„……ƒ‚‚€€€€€~€€€€€€…ˆ…‚‚‚‚‚€€€~€€€€€€~}|}~€~~}}~~~~}~~}}~~~~}}}~~~~~~~~€€€€€‚‚‚ƒ‚ƒ…„„…‡‡††††††‡†…††††‡ˆˆ‡……††‡ˆ‹Š‰ˆˆ‡†ƒƒ‚‚ƒƒ‚€€€‚€€€€€€€€„ˆ†ƒ‚‚€€€€€€€€€€€€€€€€~|}}||}|}~~}~~~~~~~~}}}~~~~~~}}~~~~}~~€€€€}~‚€€€€€€‚ƒƒƒ…………………†††††ˆ†………„…‡ˆˆ‡††††ˆ‰‹‰ˆ‡†‡ˆ…ƒ‚€‚„ƒ€€€€€€€€€€€ƒ‡…‚‚‚€€€€€€€€€€€€€€€€€€€|{}}~~~}{}~~~~~}~}{{||}}}}}|||||}}}}}}}}~}|{{€€€|{z€€€€€€€€€‚ƒƒƒƒ„……„„……†‡††‡ˆ†„…‡„…‡††‡‡††‡‰‹‹ˆˆˆ‡‡‡ƒƒƒ‚‚‚‚€€€€€€€€€€€€~€†‡‚‚‚€€€€€€€€€€€~|~€€ƒ„‚}}|}~~~~~}|||{|}~}}|}}|{{}}}}}}}}}|z€€‚€|{€€€€€‚ƒ„‚‚„„„„…††‡†‡‡‰…„††††††††………‡ˆ‰‰ˆˆˆˆ‡ˆ„ƒƒ€€€€€€€€€€€€„‡ƒ‚‚€€€€€€~{}‚„ƒ€€€~~~~~}|}}{{}}}}}|}}{{}}|}}}|}€}}{€€||€€€‚ƒƒƒ‚ƒƒ„„„…„…†‡‰†‚ƒ…†††‡‡†……‡‡‡‡†‡‡‡ˆˆˆ‡€€€€€€€€€€€~€€€€€…„„€€€€€€€€€€€€}|~†~~~~}~~~}}}||}~~}|}}}}|{|}}|}}}~~}|}€~~|€€€{}~~~€ƒ‚‚„ƒ„„„„„ƒ„†ˆ‡€ƒƒ‚„…†††……†‡‡ˆˆ†††‡ˆˆˆ‡€€€€€€€€€€€€€€€€€€€€††…ƒ€€€€€€€€€€€€€€€€€||ƒ‚~}}~~~~~~~}}}|}{{}€~}~}}}}}}~||}~~~}}€~}{~€~~{}€€€€€‚ƒ„„„„„„…ˆ‹‚~€‚‚ƒ†‡‡††…†‡‡‡ˆ†‡‡‡‡‡‡‡€€€€€€€€€€€€€€€€€€€„‡„‚€€€€€€€€€||‚€~~}~~~~}}}}||||}}~€~~}|}}}~~}~~{||}~~}}~~}}~~‚|}€€€€€€€€€€€€‚„ƒƒƒ‚‚„‰‡‚ƒ‡†……………‡‡‡‡‡ˆˆ‡‡‡‡ˆ€€‚€€€€€€€€€€€€€€€€€€‚†ƒ€€€€€€€€€~€€~~~€~~~~~~}}}}}}|{|}€~}}~}}}}|}|}|{|~~€~~~~~~ƒ€||€€€€€€€€€€€€€‚‚€‚ƒ…Š‚€€‚……ƒƒ„„†††‡‡‡‡‡‡ˆˆˆˆ‰€€€€€€€€€€€€€€€€€€€€„„ƒ‚€€€€€€€€}€~~~~~~~}}}~}|||€~~}}}|}|||}{||~{{{}}~}€€~~~}{}z|€€€€€€€€€€€€ƒ‰†€€ƒƒ‚ƒ„††††‡‡ˆ‰ˆ‡‡‡‡‰‰€€€€€‚€€€€€€€€€€€€„†„‚€€€€€€€~~~~~~|~~~}}~}}}}}}||~€}||||||}}}|~~}{|||}~~~}{}‚~{|€€|y}~€€€€€€€€€€€€€ƒƒƒ‡‰€€€ƒƒ„…†‡……„…‡‡‡‡‡†‡‡‡ˆ€€€€€€€€€€€€€€~€€€€„„ƒ‚€€€€€~~~}~~~}}~}~~||}|{}}}|}~~~}{||}~|z{{|}|}}|||||}~€}|||€‚~|z~‚~z}~~€€€€€€€€€€€‚‚…Šƒ€€€€€€€ƒ„…††…„„„‡‡‡‡‡ˆˆ‰ˆ‡€€€€€€€€€€€€€€€€€„ƒƒƒ€€€€€~~~~~~}}~~}{z{|||}z{}~}}~~}|{z|{||||~~{{|~~}{}~‚z{‚{|~€€€€€€€€€€€‚‰‡€€€€~~ƒ„…†††…†‡‡‡‡‡‡‡‡ˆˆ€€‚€€€€€€€€€€€€€€€€†„„„‚€€€€€€€~~}~}}}~~~€||}~~~~}|}}||~}~~}|||||}y|}{|~€ƒ{|€€€€€€€€€€ƒ†‰‚€€€€€€€€ƒ…ƒ„…†‡††††‡ˆ‡‡ˆ‰‰€€€€‚€€€€€€€€€€€€€€€€„ƒ‚€€€€€€€€~~~~~}~€€€}|}~}||~~}}}}}|||~~~~|||}{{€~{x{}}~~~‚~{|€€€€€€€€€€€ƒ‰„~€€€€€€„„ƒ„…†…†‡……‡ˆ‡‡ˆ‰€€€€€€€€‚€€€€€€€€…€‚„€€€€~~~~~€€€}}~~~~}||{|||{|~}{}}|}}|}}}}{z~{y{|}}}~}~~zz~€€~~€€€€€‚…‰€€€€€€€‚ƒ„„…†‡‡…†ˆ‰Š‰ŠŒ€€€€€€€€€€~~€€€€€€€†€ƒ„‚€€€€€€~~~~~~~}}}}}}}~~~~||}}}}|{{}||||{|~~}~~}}}}}}}}zz~}||||}}}}}€||~€€€€‚…‡‚€€€€€€‚„„…†‡ˆ‡ˆ‰‰ŠŠ‹Œ|~€€€~€€€€€€€€€€€‚ˆ~„„‚€€€€€~~~}~~}}}}}}}}}}}}}}|{|{z|{z|}~~}}}~~~}}~~||}}}}||}}~~~~~€€€€‚„„‡‚€€€€€€€€~ƒƒ„…††‡‡ˆŠŽŽŽqrruy{~€€€€€€€€€€‚‰ƒ€ƒ„ƒ‚€€€€€~~~~~~~}}}}||}~}}}~}}}}{{z{{{{||||||||}}|||||||||||}}}~~~~~~~~~~~~€€€€„…ƒ‡€€€€€€€€€€€~‚„„†‡‡‡‰ŒŒŽŽŽŽmkkmnnv€€€€€€€€€€€€ƒˆ…‚‚‚€€€€€€~~~~~~~~~~~~}}}}}}}||||}}}||{{||||}|||||{{{{{{|}}}}||}}}}}}}}~~~~~~~}~€€€€€€€€‚…‚‚ˆ€€€€€€€€ƒ†‡‡‰‰‰Œ‘‘kjjkkjr€€€€~€€€€€€€€€€€€‚†ƒ‚‚‚‚€€€€€~~~~~~~~}}|}}|}}}|}{{|}}||||{{{{|||||}}|||}|||}}|||}}~~~~~}}~~}~€€€€€€€„‚€ƒˆ‚€€€€€€€€€€€€€„†ˆˆˆŠŒ‘’’kjhhigr€€€€€€€€€€€€€ƒ†ƒ‚‚‚‚ƒ€€€€€~~~~~~~~~}~}}}|}}}|{||{|}}||||{{{{{|||||}}||}||||||||{|}~}}}}}~~~€€€€€€€„„‚‰‚€€€€€€€€€€€€€€€€€ƒ…‡‡ˆ‹‘’hhhhhdp~€€€€€€€†‡€€‚€€€€€~~}~~~}}}|}|||||||}||}}{z|||||{||}|{||{{{||{||}}}|{|||}}}~~~~~€€€€‚„‚ƒ„‰‚€€€€€€€€€€€€€€ƒ„†ˆ‰ŒŒ‘fffefbp€€€€€€€€€€€€€†ˆ€ƒ€€€€~~~~~~~}}}}}}||||||||||{|~}}{z~}}{z~}{{{{z|}}~}}|}}}}}}~~~~~~~~€€€€€€‚ƒƒ‚„‰‚€€€€€€€‚„†ˆ‰‹ŒŒŽffedd`p~€~€€€€€€€‡‰€€€‚‚‚€€€€€~~~~}~~~~}}}~}}|||{{||{{|||}}~~~}~}z}{z|}~}z|}|||||}}}}|}}}~~~€€€€ƒ„„ƒƒ‡ƒ€€€€€€€€€€~€€‚†‡‡ˆ‰‹ŒŽeeeecbr€€€~~€€€€€€€€€€ˆˆ€€€‚€€€€€€~~~}~~}||}}}}|||}}z|~}~~|}}~}~}{||y|||~xz|{||}}}}~~}~~~~~€€€€€€€€€‚ƒ‚‚‚‚‚†„€€€€€€€€€€€€ƒ††‡ˆ‰‹ŽŽccccb`q~~€€€€€~€€€ˆ‡€€€€€€€€€€€~~~~||}}}}|||~|{}}}}}|{{~|{{{{|y{|}~x|}}||}}}}~~~~€€€€~€‚‚‚ƒ‡„€€€€€€€€€€‚„„‡‡ˆŠŽŽŽŽedbaa_p€€€€€€€€€€€€€€€ˆ‡€€€€€€€€‚€€~~~}}~~~~~~}}}}}|{{{{||}}|z{}}|}}}}}}{{|{|}|zz~~}|{}~}}}~~~~~~}}~€‚‚€ƒ‡†€€€€€€€€€~……†‡‡ŠŒŽŽdddcb_q€€€€€€€€€€€€€€~€€Šˆ€€€€€ƒ‚€€€~||}}~~}}}}||{|}}||{|~}{{}}|||{|}|{{}|{{z{~~{{|||||||}}}~~~~€~~~ƒ‚€€‚ƒ‰‡€€€~~~~€€€…†…‡‡Š‹ŒŽcccca`p€€€€€€€€€€€‰‡€€€€€€€€€€€€€€€}}}}}|}}|}}|{|}}||}}}}|||}|}}{{{|~}}}~~~~}{|}}}}||||}}~~~~~‚„ƒ€‚‚ˆ‡~~~ƒ†‡‡‡ˆŠ‹ŒŽcbbccan€€€~~€€€€Ї€€€€€€€€€€€€€€€€~}}}||||}}|{{{{||{{}~}|||{{}}}}}}||}~~}}|z{}}|||{{|}}~~~~€ƒ‚‚€€€‡‡€€~~‚†‡‡‡‰‹Ždddcbal€€~€€€‚€€€‚Š…€€€€€€€€€€€~~}}~~}}}||||{{{{||{{{||{{{zz||||{{{|||{{|{{{{{|}}}}}}~~€~~~~€‚‚€€€€‡‡€€€€€€€€~ƒ†‡‡ˆ‰‹ŒŽ’cccbbaj~€€€€€€€€€~‹„€€€€€€€~~~}~~~}|}}||||}}|||||||{{{{{|{{zzzz{{|{{{{{{{{|}}}~~~~~~~~~~~€€€€€€€€‡‡€€€€€€€€€€€~€„†‡†ˆ‰‹Œ•dcccdcj~€€€€€€€€€€€Š…€€€€€€€€€€€~~~}}||{|}}}}}|}~~~~~}}}}|||{z{{{{}}}~}}}}||||}}}~~~~~}}~~~€‚€€€€€‚†‡€€€€€€ƒ‡‡†‰ˆˆŒ“•ddeeecg|€~€€€€€€€€€€€ˆ„€€€€€€€€€€€€€€~~~~}}||||}}}}{{||{{|z{}~}|}|{|}~~}zz||z{|~~}}}}}~~}~~~~}~~ƒ„€€€€€ƒ€€‡†€€€€€€€€€€†††‰‰ŠŒ’–eeeffdf{€€€€€€€€€€€€€€‰„€€€€€€€€€€€€~~~~}}|||}}|}|zzzzz{{}~}}|{|~}z}}|||{{z{|~~~|}~~~~~~|||}~€€‚„‚€€€ƒ€‡‡€€€€€€€€€€ƒƒ‚…†‡‡ˆ‹Ž‘•fghfhfgz€€€€€€€€€€€€€€Š…€€€€€€€€€€€€€€€€~~~~~}}}||||||}}|{zzz{{{|~}|{~}z{}{zz{{{|}z|~}}}}}}~~}|}~~€ƒƒ€€€~‚‡‡~€€€€€€€ƒ„‚ƒ„…†ˆ‹“efffgegx€€€€€€€€€€~‚‹…‚‚€€€€€€€€€€€€€~~~~}~}|}}}{{||~~}}}{|~|}}||}{||z{}~~}}|~}z|}}}~~~~}}}}~€€€€€€€€ƒˆ†€€€€€€€€€€€€„…„†‡‡ŠŽ‘effhjjiv~€€€€€€€€€€‚‹„‚€€‚€€€€€€€€€€€~~~}}}}|}}}{{||~}|||||}|||||||{{{|}}||}~}}{|~~~~~~~~~}}~€€‚ƒ€€€€~€ƒ€€ƒŠ„~€€€€€€€~€€‚„……†ˆˆŠ‘ijkjkkks~€€€€~€€€‰…‚€€€€€€€€€€€€€€€€~~~~}}}}{{}}~|{{||}|}{{|}~z||{{|}}}~~}|{}|}}~~~~€‚€€~‚ƒ€‰ƒ~€‚€ƒ‚„†ˆ‰‹jjjjloms}€~€€€€€€€€€€‰…‚‚€€€€€~€€€€€€€~~}}}~|y}~~|||||}|}|{~||||}}}|||}}~~}|{|{|}}}~~€€€‚€€€€€‚ƒ‰ƒ€€€€€€€€€€€€€€‚„…‡‡Šmlmklmnu~€€€€€€€€€€€Š„‚€€€€€€€€€€€€€€€€€€~}}~~~~|{}|}}}}}|}}~|{}z{{|}|||||||}€~|z}~}}}}}~€€€€€€€€„€‚Šƒ€€€€€€€€€€€€€€…†‡‡ˆŽnmoonoqu}€€€€€€€€€€€‚‰…‚€€€€€€€€€€€€€€€€€€}}~~|}~}}}}}}}}}~|{}{|{{|{{||||}~€|zz}}}}}}~~€€€€€€€€€€€€„€€„Œƒ€€€€€€~€€‚‚ƒ„ƒ…†‰noopqpps}€€€€€€€€€€€€€…‡„‚€€€€€€~€€€€€€~~~~{|}~}}}|}||{|~{{||}||||}}~~|{z}}}}~~€€€€€€€€€€€€‚„€„‹‚€€€‚‚ƒƒ‚ƒ†npnnqrrs|€€€€€€€€€€€€€€ˆ„‚‚€€€€€€€€€€€€~~~~}||}~}}}~}|||~}{z||}}~}}}€||z|}|}~~€€€€€€€€€€~€€„ƒ€„‰€€€€€€~€€€‚ƒ…†oonprstu{€€€€€€€€…Šƒƒƒ€€€€€€€€€€~}~}}}€€~|}|}~}|z{{}~}}‚€|zy|}}~~~€€€€€€€~‚…‚€€…ˆ‚€€€€€€€€~€€€‚…†qrqrssuvz€€€€€€€€€€€†ˆƒ‚€€€€€€€€~€~~~}~}}~~~|}€~~}|}}|{~~~}{z||~€€€€~|y|}}}~~~€‚€€€‚„€€€…‡…€€€€€€€€€€€€ƒ‚‚‚‚rsrsuvvwy€€€€€€€€€€‚‰…€ƒ‚‚€€€‚‚€€~}€~~~{}~|z{|}}|{|}}}}|{|~}||||||{x{}|~~~~€€€‚€~€€„ƒ€„‡‰‚€€€€€€€€€‚ƒ‚‚‚€rrsuwuvwy€€€€€„Š„ƒƒƒ‚€€‚€€€€€€€~~€€~~€€~}}{{{{{{zz{z|~}||}}}~|{|}}}}|zx{~}|~~~€€€€~}}€‚„„…ˆƒ€€€€€€€€€€€€ƒ„‚‚ƒ‚‚‚stuvxwwwy~€€€€€€€€‰‹ƒ‚„„ƒ‚‚‚€€€€€€€€€€€~~}}~~~~~~~||{||{{{}~}}}}~}}}~~}|{yyxxy}~~~~~€€‚€€€€€~~€€ƒƒ‚‚‚„„††€€€€€€€~€€€‚€ƒrtuwxxxxy}€~€€€€€€€€‚‹ˆ‚‚„„ƒƒ‚‚‚€€€‚€€€€€€€‚~~||}}}}~~~~~€~~}}~}}}}~~~|}}}}}}~€€€€€„ƒ€€€€~€€…ƒ‚ƒ„„…‰€€€€€€€€€‚‚„ƒ€ƒtuwwxxyz{|€€€€€€€€€€€€„‹…ƒ„„„ƒƒ‚€€€‚€€€€€ƒ€€}}|}}}}~~~~~~~}~~~~~}}~~~~}~~~}~~}}}~€€€€ƒ‚€‚‚€€€€€€€€€‚…€ƒ„ƒ„‰„€€€€€€ƒƒ‚ƒƒƒ‚€wwxyyyz{{}€€€€€€€€€€€€‡‹†„„„„ƒ‚‚€‚‚€€€€~~€€€€€€~}||}}}}}~~}}}}~~}}~~}~}}}}|||||~~€€‚„‚~€€€€€€‚„€€€ƒ„ƒ„††€€€€€€€€€€€‚ƒ‚‚‚‚ƒ‚€‚‚‚wxxyy{~€€€€€€€€€€€‹‹†„‚ƒ…„ƒ‚‚‚‚€€€‚€€~~‚ƒ€€€}~€~}}}|||}}|}~~}}~~~~}}}|}}}||}}~~€€‚„‚‚„~~~}z~€€€€‚„‚€€‚„ƒƒ…‰€€€€€€€€€€€‚„‚‚‚‚ƒƒxz{|~~€€€€€€€€€€ƒŒ‰…„‚„†…„‚‚ƒ‚€€‚€€}}„ƒ€€}}}€€~~}|~}}}}}}}}}}|||}€‚ƒƒ‚€€‚|…„|}}|x}€€€€€ƒ†ƒ€‚ƒ…ƒ‚……Š„€€€€€€€€€€€€‚‚ƒƒƒ‚ƒ„ƒƒƒ{~€€€€€€€€€€€€€€…Œ‡……ƒƒ„„ƒƒ‚‚‚‚€}}€„„€€~}~€~||~~~~~~}‚€~}}~~~€‚ƒ~|ƒ†€|~€}|€€€„„‚€€‚‚‚…ƒ‚„…‰…€€€€€€€€€€ƒƒ‚‚‚‚‚‚„ƒƒ‚€€€€€ƒ‚€€€€€‚ˆ‹††…ƒ‚ƒƒƒƒƒƒ‚€‚‚‚~}…‚}|~€€~~€~~~€€€~€ƒ€|zyxxx{~~|€ƒ‚|}…‚~~€€€€€€€‚…ƒ€€‚‚ƒ…‚ƒ……‡ˆ€€€€€€‚€€ƒƒƒƒƒƒ‚ƒ„ƒƒ„ƒ‚€€€€€€€€‚‚Š‹††…„‚„„„ƒƒƒƒ€€€€€~~€~ƒ…€|}€€€~€€€~ƒ€|xwy|~}}{{z|€ƒ€|€‚}~€~€€€‚„„‚€‚‚ƒ„…‚ƒ……†‰„€€€€€€€…ƒ‚„„ƒƒƒƒ„„‚„„ƒ€‚‚‚‚‚‚€€€€‚€€€€‚‹‰†††„‚ƒ„„„ƒƒƒƒ‚€€€€€€€€~‚}†}~€€€€€}|{}}€~ƒ€}yy~€€€€{{|{|}€€€€€€€‚‚ƒ…‚€€‚ƒƒ„„‚ƒ„…†‡…€€€€€ƒ„†„‚„„ƒƒƒ„„ƒ‚‚‚ƒ‚„……‚€€€€€€€€€€…Œ‡‡‡†„‚ƒ„„„ƒƒƒ‚‚€€€€€€~‚|‚€}|~‚‚€€€||{}}~€€€€‚|zz€€€€€€€‚{|€~{|€€€€€€€€€€‚‚„„€‚‚‚ƒ„ƒƒ„„…†‡ˆ€€€€€ƒ€€‚„††‚‚„ƒ‚ƒ‚‚‚ƒ„ƒ„„ƒ„‚€€€€€€ˆŒ†††…„‚ƒ†……„„„„‚€‚€€€€‚‚}}~||‚‚€€€‚||~~}€€€€€€|}{€€€€ƒ|}‚~{~€€€€ƒ„ƒ‚€‚‚‚ƒ…„ƒ„„…„†Šƒ€€€€‚ƒ€„…†„ƒ…ƒ‚ƒ‚‚ƒ‚‚ƒƒ„…………„ƒ€€€€€€€€€Š‹†………„„…††…„„„„ƒ‚€€€}€€~|}‚‚€€€€}}}€|~~‚€€~}}€€€€‚„„ƒƒ|~ƒ|€€€€‚‚€€‚…„€‚…†„ƒ„„„…†‰‡€€‚‚‚„†…„‚„…ƒ‚‚‚ƒ„…‚……„ƒ‚„†„„€~€€‚„‹Š†††……ƒƒ…†…„„„…„ƒ‚‚‚‚‚‚~€}ƒ‚€€~}~‚‚}|}ƒ‚~|‚€€€€€€€~{~€|{‚}€€‡ƒ‚‚ƒ…‚€€€‚ƒ‚‚…†„‚ƒ„………‡ˆ‚€€‚ƒ„„ƒ……„…„ƒƒƒƒ‚‚ƒƒƒ„‚„„…†……„‚‚€€€€€€€„Љ‡‡‡‡†„„„„„„„„„ƒ‚‚‚‚~~ƒ|€|„‚€€~~…„„…|}€‚‚€~~€‚€€€{|~}{€}z~}~„„„‡†y|ƒ„ƒ‚‚ƒ„ƒƒ…†ƒƒ„„……†‡ˆ†€€„…„ƒƒƒƒƒ…‡ƒƒ‚‚‚‚ƒƒƒ…„……„…††…„„ƒ~€€€€‡‰‰‡‡‡‡†ƒ„………„„„„„‚€‚‚€|z‚€}€{~~~~~€€~~‚‚‚€~~€~~€~xx|}}}€ƒ~z}{{ƒ„ƒ€}y~€„…ƒ‚ƒ‚‚ƒ…‡…ƒƒ„„…†‡‡ˆ‡€€ƒ„ƒ‚ƒƒ…„‚‚‚ƒƒ†††††‡‡‡†…‡†€€€€€‚ˆˆ‡‡‡‡††„„††…„ƒ„„ƒƒ€‚€€~z~€€~|~€€~}}|{{}}}}€€€}}~€ƒ€~~~€„€‚ƒ~|z{}~~|z€‚„„ƒ€‚‚‚„††……„„„…†…†ˆˆ„€‚„ƒ‚‚„ƒ‚‚„„‚‚ƒ‚‚„„††………†††‡†††€€€€€€†ˆ‡ˆ‡‡‡‡‡†„„„………„„„ƒƒ‚‚€€€€€|~‚ƒ‚€€~}~|{{zzz}~€€~|}~„ƒ€……~|}~€€|{{{yz„…„‚‚‚‚ƒ„††…„„„„„………†‰‰€€‚ƒ‚‚ƒ„…ƒ…†ƒ„ƒ„……ƒƒ„†††…„†††††‡†€€„Œ‡‡‡‡‡‡†††„ƒ„…„„„„„ƒƒ‚‚€€‚€z{€ƒ|~~€€~€ƒ|z}‚ƒƒ„‚€~{}‚‚~}‚‚‚~{{|€€ƒ…„‚€‚ƒƒƒ„„†ˆ…„ƒƒ…†‡‡…†‰Šƒƒ‚„„………‡……††††„‚ƒ‡†ˆˆ‡‡‰ˆ††‡‡ƒ~€€€‡Š‡†‡‡‡††††„‚…††…………ƒƒ‚‚€‚‚€‚€}}€}yy{€€€€€|{|~}{}~~€€{{€‚ƒzz‚‚‚€€‚ƒ„„ƒ‚‚ƒƒƒ‚ƒ„…‡†„„………‡‡‡††ˆ‰‚€€‚ƒƒ„………………‡ˆ†ƒ‚ƒ†‡‰‰ˆ‡‡‡‡‡‡ˆ†€€€‚‰‡‡‡‡ˆ‡‡‡‡‡…ƒ…‡†††††…„ƒ‚‚‚ƒ„ƒƒ‚‚‚€€€€€€€€€€€€€|zz{}€€€~{||}}~}{|€|}€€€€‚‚ƒ……„ƒ‚ƒƒ„ƒƒ…††…ƒƒ„††‡‡‡‡‡‡‰‡…„„„ƒ‚ƒ…………„ƒ……ƒ‚ƒ„‡…†‡†„„†‡‡†‡‡ƒ€€‚ƒ†Š‡ˆ‡‡‡††‡‡‡…‚„†††††††……„ƒƒƒ„„ƒ‚‚€€€€€€€€€€€€€€€€€€€~|yyz{~‚€‚‚€€€‚‚„†…„ƒ‚‚„„„„…†‡†…„„„„†‡‡‡‡‡ˆˆˆ†„‚ƒ……ƒ‚€ƒ…††…ƒ„†‚ƒ„ˆ‡†‡ˆ‡‡ˆˆˆ‡‡ˆ„€ƒŠ‰‡‡‡ˆˆˆˆˆ‡‡†„ƒ…†‡…………………„‚‚ƒƒ‚‚‚‚‚‚‚‚‚‚€€€€€‚‚€€€€€€€€€€€€€€€€€€€‚ƒ‚‚‚‚€‚ƒ‚‚„„„ƒƒ‚‚ƒ„„„ƒ…†ˆ†ƒ„…………††‡‡†‡ˆˆ‡ƒ‚‚†…ƒƒ‚ƒ‡‡…†„ƒ…‚€„‡‡‡‡‡‡ˆˆˆ‰ˆˆˆƒ‚‡Œˆ‡ˆ‡‡‰Š‰‡‡ˆ†„„…†††……†………„‚ƒƒ‚ƒƒƒ‚‚‚‚‚ƒƒ‚‚‚‚‚‚€~€€‚‚‚‚‚€‚‚‚‚‚‚‚‚‚ƒƒƒƒ„……„ƒ‚‚ƒ„„„…†‡‡…„…†…†‡‡‡ˆˆ‡‡‡ˆˆƒ‚†…ƒ‚ƒƒƒ„……ƒƒƒ‚ƒƒ…ˆ‡ˆ‡†‡‡‰ˆ‡††…„€‚‹‰‡‰‡‡‰‰‰‡‡‰‡„ƒ…‡‡†††…†††…„„ƒƒƒ‚ƒƒ‚‚‚‚‚‚€‚‚€€€€€€‚‚‚‚‚‚‚‚ƒƒƒƒƒ‚‚‚ƒ‚‚‚‚‚‚‚ƒ„…„„„ƒ‚‚ƒ‚ƒƒ…††‡†„„„…††‡††††††‡ˆ‰†„ƒƒ…ƒ‚„„„…†„„ƒƒ‡…„‰‡‡‡†††ˆˆ‡‡‡‡†€~‡Š‡‡ˆ‡‰‹Šˆˆˆ‰ˆ…ƒ…††‡††††††……ƒƒ‚‚‚ƒƒ‚‚‚ƒƒ‚‚€€€€€€€€€‚‚‚ƒ‚‚ƒƒ‚‚‚„„ƒ„ƒƒ‚‚‚‚€€‚ƒƒƒ„††„„„ƒƒ„ƒƒ„…††‡…„…††††††…††††‡ˆŠ‡„†††…ƒƒ„†…„…………„…„ƒŠˆ‡††‡‡‡……‡‡‡‡…‚ŠŒŠ‡ˆ‰ˆˆŠ‰‡‡‰‰ˆ†„„……‡†††‡††††„ƒ„„ƒƒ‚‚ƒƒƒƒ‚‚‚‚‚€€€ƒƒ‚‚‚ƒ‚ƒƒ‚‚‚‚ƒƒ‚€€€ƒ„…„††„ƒ„„‚‚„„„„…††‡‡„„…„…††‡ˆ‡†††‡‡‡‰‰ƒ‡†…„„††††…………………… \ No newline at end of file diff --git a/libs/ultrahdr/tests/data/raw_p010_image.p010 b/libs/ultrahdr/tests/data/raw_p010_image.p010 deleted file mode 100644 index 01673bf6d5bbfd11737acccd484b981ac6f8fbf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2764800 zcmZShJqkxdU^E06K8-@UhQJMmQAp1aIL9ywM?+vZhrlC-kq1XxZ=)eF8UoY^fzkFK zeM5krZJ$xQZj846Mnhnjhrq~d{XH5Tw;2tA(GZ|Y2++6nGiuvtn~c68Fq}tzM*TJ# z0_R5H=-+7jj~XLCqh^eTz;C+FeU7&NK8?=)(m4c1TL_~eFuX%xbo_6$9v=;X(GVan z1V-Cr^bG;}j(&~WHoWWpQQwV*z-arAyqa%R*Jucgh5&tAKclvdw#euk0;BDN(GVEk zAu!th8?D7hLtr!n$P0nd78!j*fWEDtQQL-h-9PHP(GVDI|B+Yojp`Z=fzc44Z|i5& zw$Th5&gXFxn!cZwS!0^)qVQ@UHtueK#5cqwPQP zYQ9ljqaiRF0`zVDjM_HZBBO5zjJ6L(LtuD^z-aq#v=$!?fzc2kF9b$gWb_RI`nG;X zZ5!To|ETXqLtwQ1M_$c0s%ta^MnizUt)EfbMq6a`4S~`2!Dt8!?+_Sm|Bcq-qaiRF z0_26jXp4-#Awb{O&!}y~yY3(L-Dn7mw*Sbh`9^h(hQMeD(6{w7YTIawjJ_c-+CCT! zf#DqjqwT-ZT6{DFMnizS5EyNd(KiI>+xi)`ZFtxHqrMvrfzkFKc{SgtuF((}4FUSL zenxE@ZIRJ81V-BjqaiT7LtwQ1H(HC2hQMeDkQV}@Ei(Fs0DW6OqqYt2x_{JnqaiTb z{v)sE8`U)$0;3^7-`3BlZKEwR`i8)0`(QK#hIa^zw*N+J@zD?%4FU2(V6;U>-w>d0 z>u1!q;a&HS`ffA?M%#bn)qJD6MnhmU1nArP8MSS+MMmEc7;PVnhQRO+fzkHgXe~Y( z0;3^7UI>h~$mkma^lkl&+BUrF{!!nJhQMh1kGz_1RM%(-jD`SxTR)?=jkd_>8v>*4 zgV7Ke-XSpB{u`~uM?+vV1jq}4(H0qfLx8@mpHbU}cilhgyU`FBZU2#1^Ns2n4S~@R zpl|DE)V9$U8GS=ww0$rd0>e85M%#a*wfJZVjD`StAu!q^qi+b%xAik>+wiXYM}0RM z0;BCe@@l?OU85l|8Uplf{fyc++9IQG2#mH5MnhnDhrnq2Z?qO44S~@RATI<)TV(VN z0s6LnMr|A3b^oaEMnhn<{YPHSH>zti1V%%EzOA28+eTYt^bLX0_Q7Zf4DS#aZU2qd z;-euj8Uo~nz-Wt%z9B&0*3YPI!@KSu_1$O)jJE&CtNBKCjfTKz2++6nGiuvti;TV@ zFxoyC4T0eu0;BD}(OP^o1V%%Eybu^|k;6&SjfTKz`;WYuZ&cT42#kgReOo`HwvD#P z=oCfzcKjeM5l0t)EfbhIidR>bubp7;XQNSM!bP z8V!Nb5TI}CXVkXQ78!j*V6=TO8Un*R1V-C`qqX>G2#kgRc_A>`BBO5z(6{w7YTNLx z`$v5@8Umy3Kk{n6QC*`UFd72%ZT*beHrgViZwQRG4@N^^c!$7f`){-s9}R)g5FjrE zMq6a`4FUSLenxE@-gW<|??yvlwEah3%{QuRGz3ONfWEDtQQJmaWb_Sz(e}Y;2n_EK z7;XQJ*5acfFd72ng}}hI$WAkiL`dJ(&!}y~yY9#3J7tEE2yunR@D?9!|B+Yojp`Z= zfzc2cZU51?6*}4?qi+Zd@46qC??z(lPniK%Xbf-h(e~eHEj}6oqai?E2#mJ>M%#b% zZIRK}w&7j(9z95Eu=C(e~eH`;WdYGWyy!{OWsRd^Fnr zBPNuFhnms$-)Jp98UmvsKwb!pw*N-kfAnpU(bu-&QOA?&i_!KUsUbFW)sMFS$gBBA zb&ZC=Xb6n9|3=$?^lg#R*S4WsuT$dk(e@uDp*OVjj<)|sYw^(#7!3jPLSVH0H`@NA zZ;On+whgVioLa6OZU0d#BnQ7aqwPQPYQ9ljqaiRF0;BD}(e@vGTV(XLZSdFMv~b~Q z`;Qi(IylW7ZU2qd;-euj8Uo~nz-aq#wEail78!kQ8=Q4Etz9+R{-bpW4<7SJ+kfQM ze51NXLtr!nM%#a*?LYdq$mna^;HjtS>5|d*A3Z~RVC@=h|Bcq-qaiRF0_26jX!~!p z{YT#x8GUUV*t&V3Y#(j^4OGa}-`>&oA9*$3sIJiv7!85Z_TOmxkG?H3`r1bS`gkzd zINJUj46#5TH;lIbMr-lW5Eu;s@6hzP8b)4jyc_jJE#?ZT}6Gn4;XBqwPQPYQ9ljqaiRF0;BD}(e@vGTV(XLjq>_+ zD4Hh5&gXFxvhbZU51?MMhuS$g5L_x~|do-%yW1;@v;m z{v)sE8`U)$0;3@?+Ws4D|IxQaMqk^AuSbWI%F*`UaEeX*ei?25jn?9$Aut*OEcMezC(e~ePjaf{8jkf>DtNBKCjfTKz2#mJ>M%#b%ZIRK}Hq82R zB#4Z*|3*SAL;N_}{u`~uM?+vV1jq}4(e~eH`;WdYGWyyEsUt@kIokdkIWa!k{v)sE z8`U)$0;3@?+Ws4D|IxQaMqk@TPCEeEr=#sZh~|3=$? z^lg#R*S6tZ_v7;2X!{RW$P7R6(e@vCHQ%VN(GVC7fzkHgX#0=8Ei(GrHvH;)Vth2( z{v#%ohKHKb_TOkNJ{kg}AwXUTjJE$q+kfWk6#AE_ZWbk&cx|H!NP zMs6hzP1gmx|~|B9c}+nDFJWu_8&b%dtmJvZU2qd;-euj8Uo~nz-aq# zwEail78!kQ8`!#eplly){|!{g)8F3F_8)mQ->9z95Eu=C(e~eH`;WdYGWyy^|N3|^ z*f`q$8w{~PA2*D)|3+)^(GVC70rEm%wEZ{Q{-bY;jJ~$frw$%$wv4v_23w5K&OM{; zKk{n6QC*`UFd71*?Z46XAAMV7^tFw4_3mIcd9?jEm}7^!ZX0d?jn?9$Aut*OCfzkHgX#0=8Ei(Gr2B{-Q8adkj8#ysP+WsT2 z<{Q;D8UmvsFxvhbZU51?MMhuSMov2b*{7rJKjhFD-t5u#-)Jp98UmvsKwb!pw*N-k zfAnpU(bu-&UH9Yi-Dvv{SI7)M@zM4lc{SgtuF((}4S~`2-)Q@fzAZBP+BW>^dt!Vv z+WsRZl!k|z(e~eHEj}6oqai?E2#mJ>M%#b%ZIRK}w&797lj@7n_8+MsHgwgGw*Sbh z`9^h(hQMeDjJE$q+kfOjKp!`Zw*N+J@zD?%4FU2(V6^==+Ww<&i;TXu(Wed`Y_^QH{{~x((9S)h z?LYErzENGHAut*OqwT-Z_8)y)Wc0O-cJ=OHHhHxDH<)9Gx^5e7|Bcq-qaiRF0_26j zX!~!p{YT#x8GUV|Ze2T6OdDG2#kgRc_A>`{u^!o(YHlLU)#v5Q-`{)(e~d^k3r(y zKid8yujU)oH5vk=Au!th8*Ts5w?#%@+la46hm*?D_TO-dP5gctZU2qd;-euj8Uo~n zz-aq#wEail78!kQ!(Vp}SJ~0_-*AmtOn;5G|H!NPMsU&~* zG}`_nCX|MUn$h;(Xe~Y(0;3^7UI>h~|3=$?^lg#R*S6tN$CK)d(e@vyAvSc?kGB8F ztNBKCjfTKz2#mJ>M%#b%ZIRK}wxL_EQ{wW`_8%poH?;JQw*N+J@zD?%4FU2(V6^== z+Ww<&i;TXu4XwJITCN>!|4}O>2fsO^?LYErzENGHAut*OqwT-Z_8)y)Wc0Od@Ymn8 zaN%hCj~1aiIL#bw|Bcq-qaiRF0_26jX!~!p{YT#x8GUUVoOL#>T{YVNqjd-m9`i@r zf8^DCqq;^zU^E0q+kd0&Kl--F=xf{Hsi*1blF{}bJwtn7?HX}2P>(_4 z-9Os?Bd_Kg)ioLdqaiTb{u^!o(YHlLU)zYUM~9Qj(e~ePicS1}8EyZK*5acfFd72n zg}`Y0Z?yeK-xe8tZNp!84p-UH_TO-gSxkS8w*Sbh`9^h(hQMeDjJE$q+kfW)XMnWt@{5abF8?D7hLtr!n$P0nd_TOmxkG?H3`q~DmBS#uJ+Ws3kF+SS< zBd_Kg)ioLdqaiTb{u^!o(YHlLU)x4bI{?|IqwPQB&=}tA(e~eHEj}6oqai?E2#mJ> zM%#b%ZIRK}w&7j(9z95Eu=C(e~eH`;WdYGWyy!{OWsR zd^FnrBPNuFhnms$-)Jp98UmvsKwb!pw*N-kfAnpU(bu-&QOA?&i_!KUsUbFW)sMFS z$gBBAb&ZC=Xb6n9|3=$?^lg#R*S4WsuT$dk(e@uDp*OVjj<)|sYw^(#7!3jPLSVH0 zH`@NAZ;On+whgVioLa6OZU0d#BnQ7aqwPQPYQ9ljqaiRF0;BD}(e@vGTV(XLZSdFM zv~b~Q`;Qi(IylW7ZU2qd;-euj8Uo~nz-aq#wEail78!kQ8=Q4Etz9+R{-bpW4<7SJ z+kfQMe51NXLtr!nM%#a*?LYdq$mna^;HjtS>5|d*A3Z~RVC@=h|Bcq-qaiRF0_26j zX!~!p{YT#x8GUUV*t&V3Y#(j^4OGa}-`>&oA9*$3sIJiv7!85Z_TOmxkG?H3`r1bS z`gkzdINJUj46#5TH;lIbMr-lW5Eu;s@ z$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d z2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nV zR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s z@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6 zT6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBA zb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ> zMr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ# zdbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso z(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC= zXb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW z5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG z-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@ z$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d z2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nV zR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s z@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6 zT6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBA zb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ> zMr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ# zdbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso(GZ|d2#mJ>Mr-lW5Eu;s@$gBBAb&ZC=Xb6nVR@lJ#dbCAG-w+t6T6xso z(GZ|d2#mJ>Mr-lW5Eu;s@G2#kgRc_A=xEuoQVGkse>qqfnfy*1cu8EqAfoDdjo|B+Yojp`Z=fzc2c znXRya^Yv(pjJ_c-*ji@vanHzU{fzo_wEZ_)i;srDXb6xO0t43)8ksiJxAik>8-3ba zgUy!FR?)}_fzkFKc{SgtuF((}4S|u_3L7|IkG9C@8v=u^Wkw(OjGWfbs82`Rf1|bd zXb6mk0C^!Wa4n&cX)}FWKclwMr@b}UY#D78jhqk|ZU2#1^Ns2n4S~@R7@4iGf%Elf zi;TV@FxXmV^l{I~Y5k1)bhQ09T8oc{z-S1N7Xky<5*nE{)3^0AY8!ppTZ7G((N@vO z34zh}A9*$3sIJiv7!84u*$NvtUyruP=oh5&gX zFmNrQk!dr1TR)?=(WkvN*lZbX6^)z_7;XQNSM!bP8V!Nb5Ez-Quz~aSXp4-#Au!ll zX7q8-$Z7qI`gFAYH(HC2hQMeDkQV|2*Ag0;Hq*EDGin=s+FOIomeE$x$O(bb_8)mQ z->9z95Eu=Ck=Y6xIA4#p$mkmagRNyoANP!$*3YOKYA!(GVD!t+0Xf^=ONXz9BH!T4wZd&&X;0 zjQVu6{Wn^RkA}c#2#^;71J@E7nKsk6^)qT4ecD@t&6d$t(Z~sb(e@vCHQ%VN(GVC7 zfsxq?8#rH&w#euk0)wq(Mj!W#oYv2%PeG2#kgRc_A=xEuoQVGkse>qqfnf zy*1cu8EqAfoDdjo|B+Yojp`Z=fzc2cnXRya^Yv(pjJ_c-*ji@vanHzU{fzo_wEZ_) zi;srDXb6xO0t43)8ksiJxAik>8-3bagUy!FR?)}_fzkFKc{SgtuF((}4S|u_3L7|I zkG9C@8v=u^Wkw(OjGWfbs82`Rf1|bdXb6mk0C^!Wa4n&cX)}FWKclwMr@b}UY#D78 zjhqk|ZU2#1^Ns2n4S~@R7@4iGf%Elfi;TV@FxXmV^l{I~Y5k1)bhQ09T8oc{z-S1N z7Xky<5*nE{)3^0AY8!ppTZ7G((N@vO34zh}A9*$3sIJiv7!84u*$NvtUyruP=oh5&gXFmNrQk!dr1TR)?=(WkvN*lZbX6^)z_7;XQN zSM!bP8V!Nb5Ez-Quz~aSXp4-#Au!llX7q8-$Z7qI`gFAYH(HC2hQMeDkQV|2*Ag0; zHq*EDGin=s+FOIomeE$x$O(bb_8)mQ->9z95Eu=Ck=Y6xIA4#p$mkmagRNyoANP!$ z*3YOKYA! z(GVD!t+0Xf^=ONXz9BH!T4wZd&&X;0jQVu6{Wn^RkA}c#2#^;71J@E7nKsk6^)qT4 zecD@t&6d$t(Z~sb(e@vCHQ%VN(GVC7fsxq?8#rH&w#euk0)wq(Mj!W#oYv2%PeG2#kgRc_A=xEuoQVGkse>qqfnfy*1cu8EqAfoDdjo|B+Yojp`Z=fzc2cnXRya z^Yv(pjJ_c-*ji@vanHzU{fzo_wEZ_)i;srDXb6xO0t43)8ksiJxAik>8-3bagUy!F zR?)}_fzkFKc{SgtuF((}4S|u_3L7|IkG9C@8v=u^Wkw(OjGWfbs82`Rf1|bdXb6mk z0C^!Wa4n&cX)}FWKclwMr@b}UY#D78jhqk|ZU2#1^Ns2n4S~@R7@4iGf%Elfi;TV@ zFxXmV^l{I~Y5k1)bhQ09T8oc{z-S1N7Xky<5*nE{)3^0AY8!ppTZ7G((N@vO34zh} zA9*$3sIJiv7!84u*$NvtUyruP=oh5&gXFmNrQ zk!dr1TR)?=(WkvN*lZbX6^)z_7;XQNSM!bP8V!Nb5Ez-Quz~aSXp4-#Au!llX7q8- z$Z7qI`gFAYH(HC2hQMeDkQV|2*Ag0;Hq*EDGin=s+FOIomeE$x$O(bb_8)mQ->9z9 z5Eu=Ck=Y6xIA4#p$mkmagRNyoANP!$*3YOKYA!(GVD!t+0Xf^=ONXz9BH!T4wZd&&X;0jQVu6 z{Wn^RkA}c#2#^;71J@E7nKsk6^)qT4ecD@t&6d$t(Z~sb(e@vCHQ%VN(GVC7fsxq? z8#rH&w#euk0)wq(Mj!W#oYv2%PeG2#kgRc_A=xEuoQVGkse>qqfnfy*1cu z8EqAfoDdjo|B+Yojp`Z=fzc2cnXRya^Yv(pjJ_c-*ji@vanHzU{fzo_wEZ_)i;srD zXb6xO0t43)8ksiJxAik>8-3bagUy!FR?)}_fzkFKc{SgtuF((}4S|u_3L7|IkG9C@ z8v=u^Wkw(OjGWfbs82`Rf1|bdXb6mk0C^!Wa4n&cX)}FWKclwMr@b}UY#D78jhqk| zZU2#1^Ns2n4S~@R7@4iGf%Elfi;TV@FxXmV^l{I~Y5k1)bhQ09T8oc{z-S1N7Xky< z5*nE{)3^0AY8!ppTZ7G((N@vO34zh}A9*$3sIJiv7!84u*$NvtUyruP=oh5&gXFmNrQk!dr1TR)?=(WkvN*lZbX6^)z_7;XQNSM!bP z8V!Nb5Ez-Quz~aSXp4-#Au!llX7q8-$Z7qI`gFAYH(HC2hQMeDkQV|2*Ag0;Hq*ED zGin=s+FOIomeE$x$O(bb_8)mQ->9z95Eu=Ck=Y6xIA4#p$mkmagRNyoANP!$*3YO< zN85j+wfJZVjD`StAuw<)p^<4beOo`Hw$Z1(HP~z!Z55525EyO$kyrDL>KYA!(GVD! zt+0Xf^=ONXz9BH!T4wZd&&X;0jQVu6{Wn^RkA}c#2#^;71J@E7nKsk6^)qT4ecD@t z&6d$t(Z~sb(e@vCHQ%VN(GVC7fsxq?8#rH&w#euk0)wq(Mj!W#oYv2%PeG z2#kgRc_A=xEuoQVGkse>qqfnfy*1cu8EqAfoDdjo|B+Yojp`Z=fzc2cnXRya^Yv(p zjJ_c-*ji@vanHzU{fzo_wEZ_)i;srDXb6xO0t43)8ksiJxAik>8-3bagUy!FR?)}_ zfzkFKc{SgtuF((}4S|u_3L7|IkG9C@8v=u^Wkw(OjGWfbs82`Rf1|bdXb6mk0C^!W za4n&cX)}FWKclwMr@b}UY#D78jhqk|ZU2#1^Ns2n4S~@R7@4iGf%Elfi;TV@FxXmV z^l{I~Y5k1)bhQ09T8oc{z-S1N7Xky<5*nE{)3^0AY8!ppTZ7G((N@vO34zh}A9*$3 zsIJiv7!84u*$NvtUyruP=oh5&gXFmNrQk!dr1 zTR)?=(WkvN*lZbX6^)z_7;XQNSM!bP8V!Nb5Ez-Quz~aSXp4-#Au!llX7q8-$Z7qI z`gFAYH(HC2hQMeDkQV|2*Ag0;Hq*EDGin=s+FOIomeE$x$O(bb_8)mQ->9z95Eu=C zk=Y6xIA4#p$mkmagRNyoANP!$*3YOKYA!(GVD!t+0Xf^=ONXz9BH!T4wZd&&X;0jQVu6{Wn^R zkA}c#2#^;71J@E7nKsk6^)qT4ecD@t&6d$t(Z~sb(e@vCHQ%VN(GVC7fsxq?8#rH& zw#euk0)wq(Mj!W#oYv2%PeG2#kgRc_A=xEuoQVGkse>qqfnfy*1cu8EqAf zoDdjo|B+Yojp`Z=fzc2cnXRya^Yv(pjJ_c-*ji@vanHzU{fzo_wEZ_)i;srDXb6xO z0t43)8ksiJxAik>8-3bagUy!FR?)}_fzkFKc{SgtuF((}4S|u_3L7|IkG9C@8v=u^ zWkw(OjGWfbs82`Rf1|bdXb6mk0C^!Wa4n&cX)}FWKclwMr@b}UY#D78jhqk|ZU2#1 z^Ns2n4S~@R7@4iGf%Elfi;TV@FxXmV^l{I~Y5k1)bhQ09T8oc{z-S1N7Xky<5*nE{ z)3^0AY8!ppTZ7G((N@vO34zh}A9*$3sIJiv7!84u*$NvtUyruP=oh5&gXFmNrQk!dr1TR)?=(WkvNYRhN{3{(h=w*Sbh`9^h(hQMeD zjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ?? zYRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?% z4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc z7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh z`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3 zwthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;l zw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp z!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{ z3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2( zVBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr# z)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h( zhQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx! zqfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J z@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q# zMMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h= zw*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIp zBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$ zpic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeD zjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ?? zYRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?% z4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc z7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh z`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3 zwthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;l zw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp z!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{ z3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2( zVBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr# z)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx!qfZ??YRhN{3{(h=w*Sbh`9^h( zhQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J@zD?%4FU2(VBlIpBhzO3wthx! zqfZ??YRhN{3{(h=w*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEc7^qr#)ZWn$pic;lw*N+J z@zD?%4FU2(VBlIpBhzO3wthx!qfZ??*lbZ|7>RJO#R7fYGur+mujU)oH5vk=Auuvq zVFTyu(H0qfLx4VY@L;oLB)0yP83tP{(8oQa?Z44ld^7|`Lx8*x7`T?u$h4Wht)Efb z=u-y|Hd{v9e}gSXXy=~M_8)mQ->9z95Eu=Ck=Y6xIA4#p$mkmaw5xXqv&p0Fzrh?k z)OFiv`){-s9}R)g5FjrE2CgMEGHs@B>u1z9>ejVG#kA4(-%yDu%H28I{v)sE8`U)$ z0;3@?GFxE-=j+iH8GS>5^7?fsnlRe_8;Y?;o|{M8f1|bdXb6mk0C^!Wa4n&cX)}FW zKclvhSEmkjU8C*4p&o<8yMMI(M_$c0s%ta^Mnhm^w!#L^*P|^m`i21U_2_U?Iokdk zPO*vKFQe_h(OP^o1V%%Eybu_;me9zwnZB)`QQPp>ox@djwEZ_+V;0k2qwPQPYQ9lj zqaiRF0wc2(HgLWkZIRJ81TgE%ksvbK{u>Fg4DsV=`){-s9}R)g5FjrE2CgMEGHs@B z>u1z9NF6!S$kF!S$cgdM_8)mQ->9z95Eu=Ck=Y6xIA4#p$mkmaBc~mJ?9WZ?qO44S~@RATI<4t|c@wZKiMQXVkXgUH9Yi-Dvv{SI7)M@zM4lc{SgtuF((} z4S|u_3L7|IkG9C@8v?_xz9+^?LSIF zZ)oWqZU2qd;-euj8Uo~nz`(VHMyAd5ZT*beHni$;YPoi_{YR~k9Q@{tw*Sbh`9^h( zhQMeDjLcTp!1;Q#MMmEc82t4&EnGO-{-Z^x4o)*i+kd0A_-F`>h5&gXFmNrQk!dr1 zTR)?=4bD27)~*_D|Is>x2aoxq?LYErzENGHAut*OBeNAYaK0XGku1!qfvuYd%J$Lr-#~>t{p}rX|B+Yo zjp`Z=fzc2cnXRya^Yv(pjJ_d2|N3|^*f`q$8w{~PA2*D)|3+)^(GVC70rEm%;95c> z(`Nd%enxGhPaQniY#D9;4YnAeoqI;xf8^DCqq;^zU^E0qW-Dyqd_CGCqi+b%uHGHY zCXcrN26OCC*KMQiztLKJGz3ONfV>bGxR%h!w3)uGpHbVWTh|U1(?;8WLnWptcjsvP zkGz_1RM%(-jE2C-Y=sS+uSZ*C^bG;Z>(`-Z!f5+%D8?FjZXRv_jn?9$Aut*OkjM_$CojTNYjkf=WdJGcp{?Yayc{SgtuF((}4S|u_3L7|IkG9C@8v?}F zqr*w%X!~zC#U_5gjJE$qYw^(#7!3jPLSW!pLL<{=`nG;XZNp!84p-UH_TO-gSxkS8 zw*Sbh`9^h(hQMeDjLcTp!1;Q#MMmEcz^pGvg2-t5ZzRMr#E+xxztLKJGz3ONfV>bG zxR%h!w3)uGpHbT&b>v7RN85iRC&owHf8^DCqq;^zU^E0qW-Dyqd_CGCqi+a|oOS@R zPe+wiF4N%h5O`;XKR8@lR8+kfQMe51NXLtr!nMrJE);CwyWBBO5z4BdL2 z5|@v*|0oH)p`~}U{Wn^RkA}c#2#^;71J@E7nKsk6^)qVQ(5lO+<=WBqAGJbq@S8K* z{v)sE8`U)$0;3@?GFxE-=j+iH8GS=w@Ymn8aN%hCj~1aiIL#bw|Bcq-qaiRF0_26j zz_o-%rp@$i{fyc+IO}X$yK1!kN9zzCJm!zK|H!NPMsbGxR%h!w3)uGpHbTewr(CM+eh1f0~PZ0 zw|BJtM_$c0s%ta^Mnhm^w!#L^*P|^m`i21g>*K*-<7oSDFvJ3V+%Vey8?D7hLtr!n z$P0miYYB}^o9Wy78MTc*b?{)bWwiY_*kXis?ip?WkyrDL>KYA!(GVD!t+0Xf^=ONX zz9B%ndUr6JJlg&n%&|jVw~e;{Mr-lW5Eu;s@QL7;+Ws5rF-W}oN85ko)qJD6MnhmU1V&~nY~Xx7 z+9IQG2oPV74kwkP?Z4p^oA~`Q+Ws4@#YaP6Gz7>Cfq`oYjZB;A+xi)`4S(G^TxCbw zf5SCqG5s~#{v)sE8`U)$0;3@?GFxE-=j+iH8GSKYA!(GVD!t+0Xf^=ONX zz9BGj+5yNu9c}+1hsN+`kGB6tYw^(#7!3jPLSW!pLL<{=`nG;XZ5!ToKQ7;mw*PR2 z%H0G;_55H(HC2 zhQMeDkQV|2*Ag0;Hq*EDGiux5tg~tDs?qiztwVV5m_OS7Bd_Kg)ioLdqaiRdTVVs| z>(Le&eM4aI)YJ5I$!PnJo}oRkc8#|GMr-lW5Eu;s@D&4lwT-%U z?NBjowEZ_!Vv2Hij<)~EtNBKCjfTKz2#m~D*ueREv_(eW5TLw%9f~H5w*Q7=tdZyD z(e~eHEj}6oqai?E2n<|HXk^+<-`3BlZRFLdLtWQs`){bnAo1=WZU2#1^Ns2n4S~@R z7@4iGf%Elfi;TV@KzuzqoK%js|Atd+;`hsF`){-s9}R)g5FjrE2CgMEGHs@B>u1z9 z{B`GWl^t#W4cC~(^w((nkGz_1RM%(-jE2C-Y=sS+uSZ*C^bG;b`f?D&4lwGC27jx=(#{Wo%Ae6;;XUd=bEYcvE#LttdK z!UoRQqb)M}hQP>a2O#@&wEc%18pE4C+Ws4@#YaP6Gz7>Cfq`oYjZB;A+xi)`ZFtxH zxO_L-{=*eA!%uv){YPHSH>zti1V%$(WVXTv&ex+YGWv$V@T>2M@zH4ekC;#z9%@G0 zf1|bdXb6mk0C^!Wa4n&cX)}FWKclt{k2;=IUyQc@NDZ-}tA4coM_$c0s%ta^Mnhm^ zw!#L^*P|^m`i8*Jt=B1W`Dpu(lF%DkdPm!TqqX>G2#kgRc_A=xEuoQVGkse>qqYsL zx|~|B9c}+nDdg$hW5bPHQN3gt;I(}U^E2C3xR=a35`sf>D&4l zwQXSQ=7F+(wEZ_wAy0pMN85ko)qJD6MnhmU1V&~nY~Xx7+9IQG2++Si9t<{)w*Ll0 zEYQadqwT-ZT6{DFMnizS5E!_Y(8#ozzOA28+vrmV4>nsy+kb;CMrh}r(e@vCHQ%VN z(GVC7fsxq?8#rH&w#euk0<^1l2eZke?Z3esJJfaCX!~!p79S0P(GVan1O~1pG%{_b zZ|i5&HtN>3L&dbw_TNy6DazeB+WsT2<{Q;D8UmvsFfvh5&gXFmNrQk!dr1TR)?=kyobzti1V%$(WVXTv&ex+YGWv!9@%89%QaRfG8&0u_-!G%>ztLKJGz3ONfV>bGxR%h! zw3)uGpHbWJ*PX*vcC`IBTw@l~U!(0m@@l?OU85l|8UiD;6*h3b9&M4)Hv};2%aI^5 z+Ws2}u?+F!X!~!p79S0P(GVan1O~1pG%{_bZ|i5&Hb@;g(#X;F-^hva(e@vCHQ%VN z(GVC7fsxq?8#rH&w#euk0wbp#fb7%J_8)R+3~%;m`){-s9}R)g5FjrE2CgMEGHs@B z>u1!q;a&HS`ffA?M%#bn)qJD6MnhmU1V&~nY~Xx7+9IQG2#mH5MnhnDhrnq2Z?qO4 z4S~@RATI<4t|c@wZKiMQXVkXgUH6arZZrf&+kfQMe51NXLtr!nMrJE);CwyWBBO5z zjJ6L(LtuD^z-aq#v=$!?fzc2kF9ZgzB{VW^rf=(K)VASW_mBE+Gz3Q5f8^DCqq;^z zU^E0qW-Dyqd_CGCqi+a|whu-_V0eeXX!~!p79S0P(GVan1O~1pG%{_bZ|i5&w&7j( zkNR#j1V-C`u1!q;a&HS`ffA?M%#bn)qJD6MnhmU1V&~nY~Xx7+9IQG2#mH5MnhnD zhrnq2Z?qO44S~@RATI<4t|c@wZKiMQXVkXgUH6arZZrf&+kfQMe51NXLtr!nMrJE) z;CwyWBBO5zjJ6L(LtuD^z-aq#v=$!?fzc2kF9ZgzB{VW^rf=(K)VASW_mBE+Gz3Q5 zf8^DCqq;^zU^E0qW-Dyqd_CGCqi+a|whu-_V0eeXX!~!p79S0P(GVan1O~1pG%{_b zZ|i5&w&7j(kNR#j1V-C`u1!q;a&HS`ffA?M%#bn)qJD6MnhmU1V&~nY~Xx7+9IQG z2#mH5MnhnDhrnq2Z?qO44S~@RATI<4t|c@wZKiMQXVkXgUH6arZZrf&+kfQMe51NX zLtr!nMrJE);CwyWBBO5zjJ6L(LtuD^z-aq#v=$!?fzc2kF9ZgzB{VW^rf=(K)VASW z_mBE+Gz3Q5f8^DCqq;^zU^E0qW-Dyqd_CGCqi+a|whu-_V0eeXX!~!p79S0P(GVan z1O~1pG%{_bZ|i5&w&7j(kNR#j1V-C`u1!q;a&HS`ffA?M%#bn)qJD6MnhmU1V&~n zY~Xx7+9IQG2#mH5MnhnDhrnq2Z?qO44S~@RATI<4t|c@wZKiMQXVkXgUH6arZZrf& z+kfQMe51NXLtr!nMrJE);CwyWBBO5zjJ6L(LtuD^z-aq#v=$!?fzc2kF9ZgzB{VW^ zrf=(K)VASW_mBE+Gz3Q5f8^DCqq;^zU^E0qW-Dyqd_CGCqi+a|whu-_V0eeXX!~!p z79S0P(GVan1O~1pG%{_bZ|i5&w&7j(kNR#j1V-C`u1!q;a&HS`ffA?M%#bn)qJD6 zMnhmU1V&~nY~Xx7+9IQG2#mH5MnhnDhrnq2Z?qO44S~@RATI<4t|c@wZKiMQXVkXg zUH6arZZrf&+kfQMe51NXLtr!nMrJE);CwyWBBO5zjJ6L(LtuD^z-aq#v=$!?fzc2k zF9ZgzB{VW^rf=(K)VASW_mBE+Gz3Q5f8^DCqq;^zU^E0qW-Dyqd_CGCqi+a|whu-_ zV0eeXX!~!p79S0P(GVan1O~1pG%{_bZ|i5&w&7j(kNR#j1V-C`u1!q;a&HS`ffA? zM%#bn)qJD6MnhmU1V&~nY~Xx7+9IQG2#mH5MnhnDhrnq2Z?qO44S~@RATI<4t|c@w zZKiMQXVkXgUH6arZZrf&+kfQMe51NXLtr!nMrJE);CwyWBBO5zjJ6L(LtuD^z-aq# zv=$!?fzc2kF9ZgzB{VW^rf=(K)VASW_mBE+Gz3Q5f8^DCqq;^zU^E0qW-Dyqd_CGC zqi+a|whu-_V0eeXX!~!p79S0P(GVC70pdeowEailR@lJVHgGZhiD4wx%me9X;`9Hg z%Fz%Q4S~@R7!84u83F^>Dj2wy(8#oT;9`2T{YUS%;K(e?MtwaR0;3@?8UmvsKzs-c zT&rMYw!#L^*8>;RqwT-Z_8;*j(x}SO5Eu=C(GVC70eXbMz_khnt|c@wZ63Io9&P`P zw*Tl+mW|pm8UmvsFd71*AwYZx3|y;VWVXTv&esDM)1&Rb(e@wlCDN$M(GVC7fzc2c z4FP(Dz`(T%2CgMEGHo8Xm>zBajkf>jQI?I`F&YA+Aut*Oqai?i2n<}SU}Uz!2F}+5 z7t^EdztQ#|@g>rz%Fz%Q4S~@R7!3h>guuYH3I?tvG%{@-xR@So|Bbf)=uwu9+A$ge zqaiRF0;3^7d;RqwT-Z_8;*j z(x}SO5Eu=C(GVC70eXbMz_khnt|c@wZ63Io9&P`Pw*Tl+mW|pm8UmvsFd71*AwYZx z3|y;VWVXTv&esDM)1&Rb(e@wlCDN$M(GVC7fzc2c4FP(Dz`(T%2CgMEGHo8Xm>zBa zjkf>jQI?I`F&YA+Aut*Oqai?i2n<}SU}Uz!2F}+57t^EdztQ#|@g>rz%Fz%Q4S~@R z7!3h>guuYH3I?tvG%{@-xR@So|Bbf)=uwu9+A$geqaiRF0;3^7d;RqwT-Z_8;*j(x}SO5Eu=C(GVC70eXbMz_khn zt|c@wZ63Io9&P`Pw*Tl+mW|pm8UmvsFd71*AwYZx3|y;VWVXTv&esDM)1&Rb(e@wl zCDN$M(GVC7fzc2c4FP(Dz`(T%2CgMEGHo8Xm>zBajkf>jQI?I`F&YA+Aut*Oqai?i z2n<}SU}Uz!2F}+57t^EdztQ#|@g>rz%Fz%Q4S~@R7!3h>guuYH3I?tvG%{@-xR@So z|Bbf)=uwu9+A$geqaiRF0;3^7d;RqwT-Z_8;*j(x}SO5Eu=C(GVC70eXbMz_khnt|c@wZ63Io9&P`Pw*Tl+mW|pm z8UmvsFd71*AwYZx3|y;VWVXTv&esDM)1&Rb(e@wlCDN$M(GVC7fzc2c4FP(Dz`(T% z2CgMEGHo8Xm>zBajkf>jQI?I`F&YA+Aut*Oqai?i2n<}SU}Uz!2F}+57t^EdztQ#| z@g>rz%Fz%Q4S~@R7!3h>guuYH3I?tvG%{@-xR@So|Bbf)=uwu9+A$geqaiRF0;3^7 zd;RqwT-Z_8;*j(x}SO5Eu=C z(GVC70eXbMz_khnt|c@wZ63Io9&P`Pw*Tl+mW|pm8UmvsFd71*AwYZx3|y;VWVXTv z&esDM)1&Rb(e@wlCDN$M(GVC7fzc2c4FP(Dz`(T%2CgMEGHo8Xm>zBajkf>jQI?I` zF&YA+Aut*Oqai?i2n<}SU}Uz!2F}+57t^EdztQ#|@g>rz%Fz%Q4S~@R7!3h>guuYH z3I?tvG%{@-xR@So|Bbf)=uwu9+A$geqaiRF0;3^7d;RqwT-Z_8;*j(x}SO5Eu=C(GVC70eXbMz_khnt|c@wZ63Io z9&P`Pw*Tl+mW|pm8UmvsFd71*AwYZx3|y;VWVXTv&esDM)1&Rb(e@wlCDN$M(GVC7 zfzc2c4FP(Dz`(T%2CgMEGHo8Xm>zBajkf>jQI?I`F&YA+Aut*Oqai?i2n=MaVASrB z5(4yY|BQ6|h|m9{Dn~p>F zK(-1-?H(y1K=1a?NVkvp{6DI4Gz3ONU^E0qLttcvz({F7#-X5d3|3_w7HtOrq5Eu=C(GVC70pdeoAX^2ac8`=0 zpm+Ocq}xY){vTC28UmvsFd71*AuuvSV5GD_M*TUEAu!V0WAyg_$Sli7eLWfiqaiRF z0;3^7dQ2#kinXb6mk0P!I(kgbAIyGKe0(7XLJ((NNY z|BtF14S~@R7!85Z5Ez*uFj86|qy8Mo5E$w0F?#!dWR_*4z8(#M(GVC7fzc2kJ_H7` zRWNGzNC^RYw|_>ueZ=SgQI(@1Fd71*Aut*OBQpd>N(*Gvp92{JBfULFZ~u?XvTW4X zqaiRF0;3@?8UnGlzy|3_7hhQMeD zjE2By2#m}S7%44~QGX6(2#oah7`^>JGRv}2Uyp{sXb6mkz-R~%9|8l}Dj2nUq=W#y z+dm`SKH~HLsLIh07!85Z5Eu=Ckr@Ibr3EtT&w&hqk=`DoxBo|GSvKnH(GVC7fzc2c z4FTdqU?5usqjry!5TJMaXQbOleEuI*IT`|^Aut*OqaiRdLtvz|Kt}yJkRdSA+hg?h z|Hv%MMtwaR0;3@?8UmvsKzs-cWUFA*?vWA#^ltx*bo+?U|D!5LLtr!nMnhmU1V&~E zjFcA0s6PiX1V(y$jNbkqnPu6iuSY{*Gz3ONU^E1X4}pPf6^zd%1;fsx)GqqqM@ zW?44s>(LMx4S~@R7!85Z5WpV-1KAcDiFVVw{WBQs!=D02Wk*9`Gz3ONU^E0qLtr!n z=p6zhu?;qmejW_%F?zd!-f4Q&zR?gE4S~@R7!85Z5Eu;s{2?%qZK089H@(|lgTX%h zDR5MFGz3ONU^E0qLtr!nMniz!Autl#U<2vr!O$L~w;Sl4rbq1?4S~@R7!85Z5Eu=C z(Gb8N0t49=8i{t(yX`d??8Bb|M`cGtU^E0qLtr!nMnhmU1n3Ltr!nMnhmU1V%$(Gz91!0wb{vHjsWE4DB&`yMf+mdepwr5Eu=C(GVC7 zfzc2c4FUWiFpzDbk!Ux)+g^jgKKvF2@F z9;3G#=$)oV?Hdh&(GVC7fzc2c4S~@Rz#jqw*%lg!cGJ7o|=8|a;;N9`L8fzc2c z4S~@R7!85Z5WpV-1KAcDiFVVw?KK$e!=D02Wk*9`Gz3ONU^E0qLtr!n=p6zhu?;qm zejW_%F?zd!-f4Q&zR?gE4S~@R7!85Z5Eu;s{2?%qZK089H@(|lgTX%hDR5MFGz3ON zU^E0qLtr!nMniz!Autl#U<2vr!O$L~w;Sl4rbq1?4S~@R7!85Z5Eu=C(Gb8N0t49= z8i{t(yX`d??8Bb|M`cGtU^E0qLtr!nMnhmU1n3 zLtr!nMnhmU1V%$(Gz91!0wb{vHjsWE4DB&`yMf+mdepwr5Eu=C(GVC7fzc2c4FUWi zFpzDbk!Ux)+g^jgKKvF2@F9;3G#=$)oV z?Hdh&(GVC7fzc2c4S~@Rz#jqw*%lg!cGJ7o|=8|a;;N9`L8fzc2c4S~@R7!85Z z5WpV-1KAcDiFVVw?KK$e!=D02Wk*9`Gz3ONU^E0qLtr!n=p6zhu?;qmejW_%F?zd! z-f4Q&zR?gE4S~@R7!85Z5Eu;s{2?%qZK089H@(|lgTX%hDR5MFGz3ONU^E0qLtr!n zMniz!Autl#U<2vr!O$L~w;Sl4rbq1?4S~@R7!85Z5Eu=C(Gb8N0t49=8i{t(yX`d? z?8Bb|M`cGtU^E0qLtr!nMnhmU1n3Ltr!nMnhmU z1V%$(Gz91!0wb{vHjsWE4DB&`yMf+mdepwr5Eu=C(GVC7fzc2c4FUWiFpzDbk!Ux) z+g^jgKKvF2@F9;3G#=$)oV?Hdh&(GVC7 zfzc2c4S~@Rz#jqw*%lg!cGJ7+jQmwf-KB-q$!90;3@?8UmvsFd71*Aut*O10DjS?LYdC1R`4AqpZ;o z7!85Z5Eu=C(GVC7fzc2cm=G9k|Bbf)1}5c=+BzBnqaiRF0;3@?8UmvsFi;^d+Ws4D z{|!{y8?|>d1V%$(Gz3ONU^E0qLttP+V6^==+Ws4uls9VYXb6mkz-S1JhQMeDjE2BK zg}`Y0Z?ye4P-$<}-q8>k4S~@R7!85Z5Eu=CfeC@p_TOmxZ(vg1sI8+RFd71*Aut*O zqaiRF0s|ESqwT-Z_TNCIy-|BdLtr!nMnhmU1V%$(Gz11F1V-C`qwT+eNqM8Tj)uT! z2#kinXb6mkz-R~zR0xc=|3=$?1C{nh?Hvt)(GVC7fzc2c4S~@R7?=u3m!hQMeDjE2By2#kinK!w0)`){=UH&AJB)ZWn$7!85Z5Eu=C(GVC7 zfq@Bu(e~eH`)^=U-l(mkAut*OqaiRF0;3@?8Uh0q0;BD}(e~d!rM*#mM?+vV1V%$( zGz3ONU^D~Ltr!nMnhmU1V%$(U_xND{WseF8<>REwEZ{Q z{u`K-H)`u>2#kinXb6mkz-S1JhQL6Dz-aq#wEZ_wX>Zit(GVC7fzc2c4S~@R7!84e z34zh}-)Q@9U{cwf#V0J=*r8U)v89*2B5pANAX4yB3rNMq1ks zDLAX)sLfiw4F4Z(*USFG}7>B%{>|dqai?f z2#mJ<=-2iGh4pCLkA7`GP*@M=dVkb!qwQKy8W?G9KahVZulYtz7!85Z5CFAFMlt=` zfS@+XD5hT<5L7>o;?Z`}a83iD_RvVfqc!(v2#kgR0h4;0p;Z9n?8{Xk(o zoa_Bjzm2wQL1|#5wf#W;rM%`FHDNRaMneG9CK<)_YXgGXB%_#qZ9q`{IEqKxNy9k} zfZ9VN4Ug8`qaiRF0+ffqXxoo|Z9h<0kGB2j*Y*R2^>D8DNBuV1t_7umk=FJD`IqvV zZ`6d*5Eu;sP@7~F)2|H(YLkp&`n3T;_2VcWZ6^)qGyrN3jWj%3bB~6=Xb4aq0;6p| z`nCN)VLjURqhH$(6xPGJ-XHbbXuB4a21Z)j59D9UYratvMnhmU1VC+)QB1!!AgE0; zis{z|1l5nDc(k1~oYMfPJv7qrXw5wu0;3^7c?gWQ{pi>B1BLZy+mC*2KTuc?=X!tC zZ=>y6P#PF%Z9kBIDX;lPO&AS<(GUQ&Nk%dK+JK-o$tb2@8xT}Kj^fdF(r``#p!U#6 z!=p9#Xb6mk0OcVt+V-Pg+Yc1hqisL>wf#V0J)G0h4;0qJx!xc3+i1HM zlmC7{e<`o|MokzEfzc2E zwMj-X{n~(_HpwWaUmFlqKaS$jcG7T81EBWMNW-Hw_h<-=h5+RuFxvK`U)v89)}w7d z`nCN)VLhDd{ZYS-wrfFYV5GJEK>nq?<{LF(Gz3ON0MsTKDBQ(R$}rL}s7*48>DLAX z)sG_)moaoQj5ItF;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr z_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>o zL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2Wpy zBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*V zjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR z^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G z;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBD zXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^< zPk;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#q zTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k z;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6og zU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9V zGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~ zQ4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ON zfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z( zU^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+ zsJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k z?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)- z-jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEu zp!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY! z1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6 z!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf z?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_F zD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1 zA1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT< z5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr z_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>o zL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2Wpy zBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*V zjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR z^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G z;~C^k;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBD zXON#qTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^< zPk;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#q zTH6ogU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k z;nA9VGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6og zU&?E~Q4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9V zGz3ONfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~ zQ4>Z(U^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ON zfbtL+sJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z( zU^E0k?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+ zsJ0)--jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k z?XZEup!U#6!=N_FD5hT<5L7>oL=0*VjWj$G;~C^k;nA9VGz3ONfbtL+sJ0)- z-jUY!1NnEf?MJ`1A1JIr_2WpyBdzTR^6yBDXON#qTH6ogU&?E~Q4>Z(U^E0k?XXeY z#Xzqx0JTX*G5y+rp!#tXdyd$%@kT=2gW5wQ4Ug8`qaiRF0+fdUz1lsa_CVU0qisL> zwf#V0Jre5wQ9q9C_U%Y(`+@vRdCfO!!e|JLhQMgM4AL&6S2%#$B%_#qZNSmC(a3Ha zjfD6IwTDI;9<8}YLtr!nC=UU8wR=YGfwVJ6+kW(G`+>rGB-H<-ejM3t+>zGy1NoQo zns3yE(GVC7fzfsuq+Ld@Z~(PQMlt=`fTL}rk=-^L3GokV4~;ZDT62$vz-S0i9s=}g z_l(*DX=jeM{pi>B1BLZSsQ*X(II`QgBdzTR@-O8z->3Z(U^E0q+hve;8NI>*)Fv6l^lJl-wv9%1+h`=jKd3!4((q`_ zJsJX|AwYQu(5u}uY7eBHIokH4U)v89)+3?*ANAwNZsU%$wjap9l-GQtCX9x_Xb6n9 z%OLGCdW8e1O)`q<*9IJI8;$I?(MX7YPk;nA9VGz3ONfbtNaSG#A_9!NWLwCzW~ zwjU_0M?(ES>c^4Y#vN&GKahVZulYtz7!85Z5EyNjLE2^X3I|Y|WE9h{4LI618rf~5 zkr4l&_RvVfqc!(v2#kgR6-s?aa}(AN|^Xps*eZ_5Y|JM|K-`q_zD({-wO;8#Q4x1V%$(v|R>im(eR6Ky8vy zOushZXxnIHw~a^AO5Yx{xxOL@&VYQks;jE2ByyA0AUqgOb9+9acxer>?fw$aFL8;ykc2epSr8Xm2= zM?+vV1Sk&ydbN8-?SZs2N85h%Yx{x1dL-2UqkbIOZQPO8_5=Bs@|thdgwYTf4S~^i z8KhlCuW$ghNk%dK+JK{NqmkV<8VT_aY7dPxJX&*)hQMeDP#yyGYWIxV18HZDw*BbW z_5+3WNT~lu{W!AQxFfCY2l6lFHQ%TSqaiRF0;BCRNV|+);Q(rrjAHt=0Y}?LBfD)h z65=1!9vW$QwB{ZSfzc44JOt>~?isZQ(#{-h`_ZrM2MX(vQ2%4|<0^(J45N5tguzH_ z`+@vRdCfO!!e|JLhQMgM4AL&6S2%#$B%_#qZ9r^oqmdvs+V&e6Z9h;vgW5wQ4Ug8` zqaiRF0+fdUz1lsa_CVU1qisL>wf#V0Jrde_*!&1;7mVVO(FPo8Z9kBIDX;lPO&AS< z(GVDImqFTP^a=-1n`9KzuMLQ;Z8Q?ZM%#WPqwNQZXHa`+q~Xz;do%<_LxA!SpjW$R z)E-DXbF}S8zqTJJtVcpy51SuB?SfG}GTMM6t?dW$FXc7gs0pJXFd71*?J`Kaj9%dY zYLkp&`n3VEwT(uC*l62tWVHQ2@eFDYjWj%3bB~6=Xb4aq0`zM4jM@WfXO6c0=-2iG zh4n~i>tXXFs9i9MM@AcPq_zD({-wO;8#Q4x1V%$(v|R>im(eR6Ky8vyOusfDwzkno z5F2g#jf}P*D4s#>p^=71Ywpny7!3i+Lx5iGo>6-s?aa}(AN|^Xps*eZZ9Qy$1horB z@yKWcj0h4;0oTp{<9_kDzwJC>|MYz>(JW1NoQo zns3yE(GVC7fzfsuq+Ld@Z~(PQMlt=`fY{nbBSCDm?Kd*oexP^;wTDI;9<8}YLtr!n zC=UU8wR=YGfwVJ6+kW(G`+>rGB((Lg`4QAE7{w!_4LH);ejxu+Uh|EbFd71*Au!r5 zgS5-&6%L>_$tb2@8xULDXe5Y@w*5v%+Yc1ap!U#6!=p9#Xb6mk0OcV-uXfLygmb z!{$d&yI>TLj5gp%Yx{xxOL@&VYQks;jE2ByyA0AUqgOb9+9acxer-T(ZKIJOHrnC7{e<`o|MokzEfzc2cZI?mXW%LRMP@7~F)2|JPt!*?C#75hGBctsHif2%JXr$rM zntL<^Mniz|5TIAPXVe}@J9D({N58foD6B_9TMwHbLG6N3JTlsVBdzTR@-O8z->3B1BLZSXzO9~BdA?4ibqBpaHO^UK>nq?<{LF(Gz3ONV6fZNHJx_5;N;s68~&@Mz6F8UmvsKzRtztKBnd52T$r+V-Pg+Yc1hBcZK_&5xjV z!6+UXZNQP%_5=Bs@|thdgwYTf4S~^i8KhlCuW$ghNk%dK+JM;FMk7INwCy)C+J2yT z2DOJq8Xm2=M?+vV1Sk&ydbN8-?SZs2N85h%Yx{x1dL*>Z(U^E0q z+hve;8NI>*)Fv6l^lJlRYa5LOvC+2Q$Y}e4;u+K)8fkd6<{k}!(GZ|K1nAZ78MOz} z&Kzy~(XZ_X3hR;3*2CsUP`h9hkBm0pNNf9n{7ZSwH)_IY2#kinXuAy3E~8gCfZ8OZ zn0{?QY;B{FAU4|e8yRgsP&|X$Ln953*4(2ZFd71shXB3WJ)`zO+L@zmKl-)(Kw&)+ z+Irah2x=FM;*rq?9BFMokbfz!`9@6`4S~@R7;Tq9+GX?#2T+@26w|K_h^=ij62wN^ zej}sp2a0DMuOOA+izsF{Xp>yY7dPx zJX&*)hQMeDP#yyGYWIxV18HZDw*BbW_5+3WNNDR}^CPHTFp5V;8*rqx{XqVuyyhD< zVKf9rLtwOB25FbkD;z*=l2J^*HXydP(MS**ZTpRkwjU^-LG7WDhDU4e(GVC70m?&w zUhSSydm!!1(Y7D`+J2z09tmwdY<>i_3r6wCXakP4wjap9l-GQtCX9x_Xb6n9%OLGC zdW8e1O)`q<*9OGaHW~?Hqiw&D(e?wyGpIc@((q`_JsJX|AwYQu(5u}uY7eBHIokH4 zU)v89)+3>f=WMn>BY z6wjdc&`86hHTP%;jD`T^AwaKo&!|0+cIIf?kA7`GP*{(Iw%({8JsC!F7>u;GAIQIy z*LFPZjCLWYJv7qrXw5wu0;3^7c?i&} z-7{(rq@6k1_M>0h4;0p;ZJ?3S1{%rnJkr{JApcTc^NpG?8UmvsFxoDIw9Duf4xl#4 zD5hTt?dW$FXc7gs0pJXFd71* z?J`Kaj9%dYYLkp&`n3T^a=T#E?<1q_3u+IIG(1{!kA}c#2v8mZ^lJBv+5>54j<)^i z*Y*R2^=KPtWVC@say*Z;wjap9l-GQtCX9x_Xb6n9%OLGCdW8e1O)`q<*9IKP?SfIi zkBqi2s68~&@Mz6F8UmvsKzRtztKBnd52T$r+V-Pg+Yc1hqivv((FPjH@jTMnejxu+ zUh|EbFd71*Au!r5gS5-&6%L>_$tb2@8*n7I3r77uGTOeN_RvVfqc!(v2#kgR(Z881?(eX#0ZNLn953*4(2ZFd71shXB3WJ)`zO+L@zmKl-)(Kw&-F1{xV{ zpphKUBdzTR@-O8z->3Jh)bAss?F(uTjWj%3 zbB~6=Xb4aq0`zM4jM@WfXO6c0=-2iGh4p9~Xk@g3Mshrlw6-6}zm(T}qb7`oz-S1J zw#y*xGJ1srs7*48>DLAv$?bwszmJTzFQ`2<((q`_JsJX|AwYQu(5u}uY7eBHIokH4 zU)v89)}w8pk#Ewh2aB+YjX5ks8+{t?dW$ zFXc7gs0pJXFd71*?J`Kaj9%dYYLkp&`n3T^YMTJm9vW$Qq{cO0h4;0oTwM{V6+I}Gaj?}mwX>C7{e<`o|MokzEfzc2cZI?mX zW%LRMP@7~F)2|IUQriTe_RvVfBQ>r;?V*u|M{DlU5Eu;s%0qx&?VeG4AnnZ2wjcf4 zexR@(scnLh*7gJWccjMkNNf9n{7ZSwH)_IY2#kinXuAy3E~8gCfZ8OZn0{@*k=iBz zwTDI;9;tB+Y7dPxJX&*)hQMeDP#yyGYWIxV18HZDw*BbW_5+3WNNp30w6-6}zaurS zM_Sttk;nA9V zGz3ONfbtNaSG#A_9!NWLwCzW~wjU_0M{1j3q_zD({vD}tJ<{5KApcTc^NpG?8Umvs zFxoDIw9Duf4xl#4D5hTwf#V0JyP2QBdzTR^6yBE>yg&>1NoQons3yE(GVC7fzfsuq+Ld@Z~(PQMlt=` zfFrd{0BR47G(1w{8q^*dX?V2e9u0xf5THB+=+*8SwFlD99Bup2uk8m4>yg?f7-?-k zkbg&NT#vN2AIQIy*L#xB%{>|dqai?f2+*tDGinc{ojKa}qhH$(6xJiP zO)%2hejxvj)VLmLZ9kBIDX;lPO&AS<(GVDImqFTP^a=-1n`9KzuMId-+XSHY&`850 zHLgMJp^=71Ywpny7!3i+Lx5iGo>6-s?aa}(AN|^Xps*gPZGw^3_5=BMq{j6~Yx{xx zOL@&VYQks;jE2ByyA0AUqgOb9+9acxer>>!+9m+ChejG6sc{Wz4~;ZDT62$vz-S0i z9s=}g_l(*DX=jeM{pi>B1BLZSZ4->Nwjap9BQ>r^TH6ogU&?E~Q4>Z(U^E0q+hve; z8NI>*)Fv6l^lJl-)HVUAJv7qrNR4YyduXKL(VBZS1V%%E@(`d`yJyrMNIP@1?MJ`1 zA1JIxYMWrBwf#W;9jS3W(%OC?|59G_jhZkT0;3@?+Af2%%jgvjpf<@Ure7Oyq_zn_ z?V*u|M`~Py+Cw7^kJj9yAut*Ol!pMl+C8K8K-!t3Z9n?8{Xk(oQriS0t?dW$??{d7 zk=FJD`IqvVZ`6d*5Eu=C(RLZ6T}H2P0JTX*G5y+rBehKcY7dPxJW}Ht)E*jXc(mpo z4S~@RpgaWV)$SR!2hz?QZTr!$?FS0$k=iC0X>C7{e@ALukF>TQ$iI}=e4{3ehQMeD zjJC@l?J|0W1E@_his{z|9I0&rPk;gK5Gp!U#6!=p9#Xb6mk0OcV-uXfLnq?<{LF(Gz3ONV6R`+@vRdCfO!!e|JLhQMgM4AL&6S2%#$ zB%_#qZNQP*CIGdEMj9TeaSdt@jWj%3bB~6=Xb4aq0`zM4jM@WfXO6c0=-2iGh4n~n z6O6RBAIQHWHLgcm+YjVl%4@z+6GlT|Gz3Q5Wsr6my}|+1CK<)_YXgqdHUX$TG}7=$ zjcZVQXr$rMntL<^Mniz|5TIAPXVe}@J9D({N58foD6B_nn_#51{XqU5sc}8h+I}Ga zQeN|onlKsyqaiTbE`zko=oJp2HpwWaUmI|wwh2J(p^=71YFvZbLn953*4(2ZFd71s zhXB3WJ)`zO+L@zmKl-)(Kw&*n+XN%6?FaJjNR8`}*7gJWm-3o#)P&Ix7!85Zb{V8y zMz3%HwMj-X{n~&dwM{VUZ%>Ai5(c35&`86hHTP%;jD`T^AwaKo&!|0+cIIf?kA7`G zP*{(YHo&MqM^1YWJ+4Pu+YjVl%4@z+6GlT|Gz3Q5Wsr6my}|+1CK<)_YXhR!m81NT z+fEv;aSv(_jWj%3bB~6=Xb4aq0`zM4jM@WfXO6c0=-2iGh4pZ)^GE$P+Kz>#fsxks z1NoQons3yE(GVC7fzfsuq+Ld@Z~(PQMlt=`fUr7plpbv_4c9aPY7dPxJX&*)hQMeD zP#yyGYWIxV18HZDw*BbW_5+3WaIN!4{WaR2g{6U!*7gJWm-3o#)P&Ix7!85Zb{V8y zMz3%HwMj-X{n~)AI&zdAZ7&ViGyrN3jWj%3bB~6=Xb4aq0`zM4jM@WfXO6c0=-2iG zh4pZ)^GE$P+Mb1_fsxks1NoQons3yE(GVC7fzfsuq+Ld@Z~(PQMlt=`fUr7plpbv_ z4c9aPY7dPxJX&*)hQMeDP#yyGYWIxV18HZDw*BbW_5+3WaIN!4{WaR2g{6U!*7gJW zm-3o#)P&Ix7!85Zb{V8yMz3%HwMj-X{n~)AI&zdAZ7&ViGyrN3jWj%3bB~6=Xb4aq z0`zM4jM@WfXO6c0=-2iGh4pZ)^GE$P+Mb1_fsxks1NoQons3yE(GVC7fzfsuq+Ld@ zZ~(PQMlt=`fUr7plpbv_4c9aPY7dPxJX&*)hQMeDP#yyGYWIxV18HZDw*BbW_5+3W zaIN!4{WaR2g{6U!*7gJWm-3o#)P&Ix7!85Zb{V8yMz3%HwMj-X{n~)AI&zdAZ7&Vi zGyrN3jWj%3bB~6=Xb4aq0`zM4jM@WfXO6c0=-2iGh4pZ)^GE$P+Mb1_fsxks1NoQo zns3yE(GVC7fzfsuq+Ld@Z~(PQMlt=`fUr7plpbv_4c9aPY7dPxJX&*)hQMeDP#yyG zYWIxV18HZDw*BbW_5+3WaIN!4{WaR2g{6U!*7gJWm-3o#)P&Ix7!85Zb{V8yMz3%H zwMj-X{n~)AI&zdAZ7&ViGyrN3jWj%3bB~6=Xb4aq0`zM4jM@WfXO6c0=-2iGh4pZ) z^GE$P+Mb1_fsxks1NoQons3yE(GVC7fzfsuq+Ld@Z~(PQMlt=`fUr7plpbv_4c9aP zY7dPxJX&*)hQMeDP#yyGYWIxV18HZDw*BbW_5+3WaIN!4{WaR2g{6U!*7gJWm-3o# z)P&Ix7!85Zb{V8yMz3%HwMj-X{n~)AI&zdAZ7&ViGyrN3jWj%3bB~6=Xb6mk0J$MR zul63gJ)>(s&6y|MnhmU1V%$(mM{pi>B1BLZK#WTp>k=FhiX#bL17L4i}4S~@R7!85Z z5EwZj0IHt{0)yHlqnLhez=4Qkn4JUN9vW%(kDStL)Tg5%Fd71*Aut*Ogx!nLpCnUjyx5a?65IeWM{T8UmvsFd70Q zCj{`-+q9GewMj-X{n~)Ej7vgh4|IEIq}e}mO0Q9$j)uT!2#kinXb6xS0)*;rTFH;L z{pi>B1BErM;*l70M_T)9p#4j3Sum<^Gz3ONU^E0qLtx~D05SD9E!2S8B%_#qZ9rPY zA+cr-bbDx|**|hhuTh_lhQMeDjE2By2#^~B#Ma@|RyW%AqhH$(6xP&^KT^yaY3;9p z_Aj|*!Kl8`5Eu=C(GVC7fsqpeq}1coR10d8jAHt=0jU{xq?$F*?V*uo|Hvu5MtwRO z0;3@?8UmvsKyC<-T9;Ex{b<{der-QcSW_$B$T4T6wZ8`1zvPw$qxwceU^E0qLtr!n zMotKjQ=d~q52#Htis{z|q(+>PYsNsghen$HBd7El_33B`jE2By2#kgRxgkJqoldE~ z(Y7D`+J2z0rZm1N(Ld7KUjyx5a?65IeWM{T8UmvsFd70QCj=;|*D28pYLkp&`n3Tm zi7QI=4s?5Hq}e}mO0Q9$j)uT!2#kinXb6xS0+iP6L^6vS3u-Xb6mkz-S1JhQP=Pfzh@Ob=x|l;fWcZ1Kl1PY4(qt(reVGqaiRF z0;3@?8Uo~oz-W5~v%NAJCe#g+k=FhiX#bL17L4i}4S~@R7!85Z5EwZjFxu9kZd+$G zJTb#_pxZ+u&Hj;7dX4&YGz3ONU^E0qLx9{67;Ud$wpT{Ogt}oe(%N4G?O$@sf>C{= zAut*OqaiRF0wX5`M%y~nZR?DNCuVpKbbDx|**|hhuTh_lhQMeDjE2By2#^~BqwN*U z_R46OP&Z6QTKj9D{Y!3HFsg4f1V%$(Gz3ONVC00rXj_N6ZJp8Z#0<}YZV!z#`$tac zHR{vR5Eu=C(GVC70dhlNw7r7aUKtG&>W0ZkYkv*2f5|NiM)i$`z-S1JhQMeDjGPb{ zZR=3Ctuq>)nBh6l?V*uo|Hvu5MtwRO0;3@?8UmvsKyC<(wpTFQE2CjT-7pzx?XQ9M zFS%vGsJ_t<7!85Z5Eu=CkrM)=Z5`^ibw}8Ya{Ylabc`8fgELTNaG!8x4Wc5Eu=C(GVCpAu!t3p>A7e zG(0iGbD-NpBhCJiQ+kd1bTkA;Ltr!nMnizy5EyN*V76CA!-TqFGSb>#1MOdO%YspT zqaiRF0;3@?8UiCH1V-CB)NSjGh9_ot4s?5Hq}e}mO0Q9$j)uT!2#kinXb6mk0P!I( z+D5}{qtP-`(qh@2aEkW&}k%sA! zjz{en4S~@R7!85Z5Eu=C(GVa$1V-Cvm~Av#hRH~4`+@vRd}09it&I8UmvsFd71*Aut*O#D~CW8x6CKM$0f6 zX>C7{e~C|xqbf&3U^E0qLtr!nMnhmU1n3b0v~25)nvL1E1ht1o8m31&9<^gM1V%$( zGz3ONU^E0qLxA`Y7;U3rw$W%ACL^uw2l6lRsc}^0Xb6mkz-S1JhQMeDjD`R`LV%WS zol&zf+m@jA&`87dNXMggjE2By2#kinXb6mkz-R~%9|EIoG|V;{EyHA_wf#W;B|bHd zsvHf0(GVC7fzc2c4S~@RphpPMvaK^}HfGxr)E*jXm>%hP)Q-^*7!85Z5Eu=C(GVC7 z0pdeow2g+@Mx$kzjI_2N$iKv=#!;1{Aut*OqaiRF0;3@?8UpkP0a~_oM$N`-TY}m{ zBMs9d9go^E8UmvsFd71*Aut*Oqai?i2#mJTFxzOf43m-8_5=Bs_|!P6ax?@+Ltr!n zMnhmU1V%%E9w9)>w$7;8m~Bf?duXI#dZgn~J4QobGz3ONU^E0qLtr!nh!26$HX3Fd zjh10D(%OC?{}P`XM^%o7z-S1JhQMeDjE2By2+$)0XxY{oH5;>S32G0GG)#|lJZi^i z2#kinXb6mkz-S1Jh5+#)Fxp1LY@^XKOh#JU59D9sQ{$-0(GVC7fzc2c4S~@R7!3h> zga9quI-_P|wk<*Jp^=8^k&Z|07!85Z5Eu=C(GVC7fzc2kJ_JVFXqas@T87C;Yx{xx zOMGe^RXG|0qaiRF0;3@?8UmvsK#vfhWm{*|Y|OSLs68~&Fg?=os2!srFd71*Aut*O zqaiRF0>p>FXd4Z)jYi8b8EI`lkbj9!jiV|@Ltr!nMnhmU1V%$(Gz91o0<>)FjGB$v zwgk0@MjEC^Iv%xSGz3ONU^E0qLtr!nMni!35EyNvVYbm|873pG?FaHN@u_iCZ)U?qM7!85Z5Eu=C(GVC7 zfzc2kJp@Kt+YjX5fol7K>?J*hMsQ5~ZpFd71*Aut*O zqaiRF0;3^7-4Fn^hejF(wZjGqQ#bZTO&bk?(GVC7fzc2c4S~@R7!3i^Ltvz}{XqU5 zsJ0)-UeaS|RL5utjE2By2#kinXb6mkz-S0iHv~ZKp^=6`?XZEu)Q!DS(?&yJGz3ON zU^E0qLtr!nMniz~5EyA~KahV1s_h4|m-HAK)iD|ZqaiRF0;3@?8UmvsFd72X4FOPl zXry6KJ8YmZbz^VTw9yb44S~@R7!85Z5Eu=C(GVa#1V&oh59Hs0YWsogB|U~lb&Q6< HhzS7z&%p-3 diff --git a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 deleted file mode 100644 index c043da6423..0000000000 --- a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 +++ /dev/null @@ -1 +0,0 @@ -ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñîçßÙØØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑËÓÊÐÎÌÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÎÏÑÒÔ×ÙÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÅÑÔÕ±F""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÄÇÍÓ×××ÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÚËÅÆÑÆ0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÒÙÛØÖ×ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÎÏÍÐÔ&+########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÈÇËÔÙ×ØÜØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÅÎÓÐÉÏ% ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÊÇÊÓÖÔÖÜØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÒÐÊÆÊÕ$1%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÑËÎØÚÔÔÚØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÍÍÑÕÎ  ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÆÌÙÝØ×ÞØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÐÍÌÍÐÆ1#########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÌÊÍÔÙØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÍÈËÕÛØÖ×ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑÆÏÑÎÇ-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÏÌÏØÝÚØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÔÓÉÈ(%!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÊÉÎÖÛÚØÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌËÐÑËÏ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÅÇËÑÖ×××ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑÌÅËÍØ"#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÒÔÕÕÖÖ×ÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÇÏÏÕËÃ#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôôéêçßÙØØ×ØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓÖÅÂÃá¿ãÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððõõïáØØÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎοÏÐÚÔÍÅÄÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòóëÝÔÖÙØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓÑÁËÐÇÝÈÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÎÌËËÌÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÏÏÇäÜâæáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÌÌÍÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÒÒÊèßâäÞââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÌÍÎÏÎÊÆÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËËÆèàâãÞââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÌÍÎÍÈ¿¸················································································································································································································································¹»»ãßâäàââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÌÍÎÎÇ»²³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³°±³áßáäâââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌËÌÐÑË¿´ºººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººººº³³µãááããââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍËÌÐÒÌ¿´²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²¸¶¶åâàâãââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÌÎÏȺ®·················································································································································································································································´µäáàãåââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÊÐε¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶·µµäââãáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÓÍÒε·¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¸¸¶áÞáâÝââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÎÒÊÎ˳µ³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³¶··äâåæâââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÐÎÑÉÎ˵¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸´µ¶ãáääßââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÌÑËÑϸ»´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´¶·ãáââÜââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËËÒÍÒε¶¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸µ··äâäåàââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÌÒËÏ˱²················································································································································································································································³³³ßÞâäáââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÐÉÏ͸¼³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³³¼º·àÝàãßââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÏÈÑÔÄÌÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÌÅêäåæâââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÔÊËÌÕÄ×ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÐÆÊÑÔÈËÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÊËÐÌÔÑËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÉÎÔÑÍÃÎÍÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓËËÇÏÙÎÓÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÆÄÕÚ׺1 ########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÛËÊÃÆÏ+,########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÇÉÙÑÊÕ($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÍÌÎÌÎ##########################################################################################################################ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÌÍÌÍÐÉ)#""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÓÔÎÃËç®ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÃÎÚÑÏÑÅÚÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍÊÐËÐÄÏÅÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎ×ÊÌÈÖÉâÌÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÉÖÑÖÃÑÀËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎËÒÆÊÊÕÞÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÍËÓÊÏÒÄÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûúóðñòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×Ú×ÐÌÍÏÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÌÑßåáàäââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüýúôñóóðññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññòòòóóóóóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÜÙÕÑÎÍÍÎÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÑÐÕßäãáâããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããææàåßãûùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùú÷òðóóðóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóòòòñññððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÔÓÒÑÑÐÑÑÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÈÈÏÚáàßàßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßâãßçâæýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøú÷òñóôñññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññóóóòññððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×ÖÔÒÐÍÌËÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÑÌÍÔàæåããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããããßáÞæáäûøûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüùôòôôñððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððóóóóóóóóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÝÛ×ÓÐÎÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÊËÒÝãáÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞÞããÞåàâú÷ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüýúóñòòîòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñññòòóóôòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÛØÔÐÏÐÒÓÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÐÍÎÕàæåããââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââåäÞãßäýüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûøñïñðíððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððððïïðððñññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÙÖÑÌÉÈÈÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌËËÑÜäääåââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââßßÚàÜáûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøúøóòõõóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòõõôóòñððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØ×ÖÔÒÐÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÒÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÒÐÓÛàààâààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààæèäëåæûöûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùûú÷÷ûýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûýüúøöôòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÔÙáéîññðòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááááúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúýùùýÿüúûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüùüùÿöñÿùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòôíöÖØÖØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØØÕÜÓóñóññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòíóîÝÓÖÙÖÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÜÖÛìöóðóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòðõðßÖÙÜÚÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÜ×ÜíöóðòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòõîÞÕ×Ù×ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜ×ÕÜëõóðòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòóóëÜÓÓÕÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÓÓÛéóòñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñëâÜÜÝÝÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÙÜÝãìòòññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòñïíìíïïññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññìíïòóñððòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòññóõõôõóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóôôòññññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòðñóôñïîòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòðññïîñóôòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûüùòñóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûú÷ôðíòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáãâàßâäåââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûûøõòðññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàââàÞßßÞááááááááááááááááááááááááááááááááááááááááááááááááááááááááûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûûúøöôóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáâäæçæääââââââââââââââââââââââââââââââââââââââââââââââââââââââââûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûüûúùøùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáâèñ÷÷ööúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûüüûûûýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáàè÷ÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââáÞåõþüúü÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüûûúúúûûúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââãÞãôýú÷ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüûûúùúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââæàåöÿýúýùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââàçÝýûüúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààààáåìó÷ùùùûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüôöúýþþüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúýþþþýüûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúùùúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþþûûúúûüýþûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùüüûúúúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýùùùùùùøøûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùøùúüýýýýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúûüúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùúùùùúúù÷ûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúúúúúûûüúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷øùúüýüûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùùùúúûûûüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüüü÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷øúûûûûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøùùúúûúúýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüýýüûùùúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûøùúúúúùøùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùùúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúúùúûù÷÷úýûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûùúûûúùöõóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóóââââââââââââââââââââââââââââââââââââââââââââââââââââââââçèéèèìõûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüú÷ôòññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññññááááááááááááááááááááááááááááááááááááááááááááááááááááááááÜÞÞÝÞåñûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûüüýüúöóñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââääãààèõÿûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûúûûùôñòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòòââââââââââââââââââââââââââââââââââââââââââââââââââââââââàâßåßâûúûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûûƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]VUW]cd^Wkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‰‡†ˆŠ†}tkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\—”‘ކynkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆ…ƒƒxj_kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‰ˆˆ‹Œ…xmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ††‰‰{pkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\……………………………………………………………………………………………………………………………………………………‚‚„‡‡~nbkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Œ€nakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰ŠŒ‹‚sfkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Œ€nakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\……………………………………………………………………………………………………………………………………………………‚‚„‡‡~nbkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽŽ††‰‰{pkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‰ˆˆ‹Œ…xmkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆ†ƒƒxj_kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\—”‘ކynkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‰‡†ˆŠ†}tkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]VUW]cd^Wkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}xtty|{x{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{`dd\ncbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzwvwz{xuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuy{_df]m`bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuwy{{yur{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{rv]eg]l]bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyqv}~{vtsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxqu]fh]j[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyypw~xuw|zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzy|agg[i[bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyqz€|tt~ŠqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqŠˆffdYi]bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyu|xot‡™²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²–mf`Vj`bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyw}vmtŒ¢¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨ªŸqf^Ukcbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyt|r}}tª©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª«_bbg`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyqy}vnu¢©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©ª¡_Ukdhbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyry}vov‹Ÿ««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««±©g[mce_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyty|vqvˆ˜ªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªª°¬m`obc]bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvyzvsw„¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯žŸh_n`c`bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxyxvuy€†ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttŠ]Zk_eebbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyzywvwy|}yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyo~WXi]fhbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy|yvvyzywxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxn]^k\cfbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy}yuvz{wszzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzu‰fdn[`cbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyzz{{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwxxyyzz{yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwxxyyzzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyywwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwvwwxxyyzyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvuuvvwxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuvvwwxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ€^^\SeY\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkntx{{yxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy{qgabdcabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\^][_gokkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkog_[]^\Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\[^`^[^fnllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllnf^[^`^[\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\Y\_][_goiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiog_[]_]Y\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\W[]\[_hphhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhph_[\][W\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\VY\\[_hpkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkph_[\\YV\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\VZ\[Z^goooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog^Z[\ZV\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\X[][Y\dkmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmkd\Y[][X\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\[^_[XYahffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffhaYX[_^[\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\]_`\WX_e^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^e_XW\`_]\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„~†ƒzˆƒZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb`h\b‚ˆ|„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ}‰Š‚‡zbYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYZ^`\VRTX\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcdeddcbbbbbbbbbbbbbbbbbbbbbbbba_aiu}}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‰Š€€…xaeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeY]`^YWZ^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccbbccccccccccccccccccccccccb`cly‚„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‰‰~~ƒv_SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSVZ_^[Z_c\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccbbaaaabbbbbbbbbbbbbbbbbbbbbbbbbadn{†‰ˆƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‰ˆ||ƒv`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\RW\\YY^c\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdcba`abb`````````````````````````_blz…ˆ‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~ˆ‡{|„yd^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^VZ^\WVY^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdca`acegaaaaaaaaaaaaaaaaaaaaaaaaa`blx„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‡†{~‡j^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^dghc[VWZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdcaacgknjjjjjjjjjjjjjjjjjjjjjjjjkiipzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ}†…|€‹„q€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€yzypd[YZ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdbabekquyyyyyyyyyyyyyyyyyyyyyyyy{xvz‚†„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ|†…|Žˆvƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‰‰†zk`\]\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcbabgnuz††††††††††††††††††††††††ˆƒƒ‰‹ˆ„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„‚€ˆU^\Z\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcb`ajy†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ|…‰{dVZcVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV________________________________________________________edb_`j{‰ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~†ˆzdVX`ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddggggggggggggggggggggggggggggggggggggggggggggggggggggggggbba^_iz‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ††yeXW\TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTdddddddddddddddddddddddddddddddddddddddddddddddddddddddd_aa_`iy…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„†„xh][]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`bdcdkxƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ……‚zphddZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiigjllkpzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†„}yurp††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††ruxwuw~…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ƒ€€ƒƒ€}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ~…ƒ~‚‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ…‚€ƒˆ‹‰†~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~…‰ŒŠ„‚…‰ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ„†ˆŠ‹Œ~~~~~~~~~~~~~~~~~~~~~~~~~‚yvy~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚€‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‡ŠŒ‰„‚‡Œƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„}xtsrr††††††††††††††††††††††††††††††††††††††††††††††††††††††††~ƒ~~„Šƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†xohdcdZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZiiiiiiiiiiiiiiiiiiiiiiiicgihfhpxƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒˆ€th_[Z[^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\\\\\\\\\\\\\\\\\\\\\\\X[][Z^hpƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŠsd[WXZTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTddddddddddddddddddddddddacecafqzƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‹‚rcZX[^ddddddddddddddddddddddddddddddddddddddddddddddddddddddddggggggggggggggggggggggggfhifdit~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŒ‚rc[Z^aVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV________________________abb_]alvƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†Z^TdV\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\bbbbbbbbbbbbbbbbbbbbbbbb_g`ck\_‡ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„ƒ~†…Ÿžœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFEPDHD=vJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ>DL\-=EKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ||||||||||||||||||||||||||||||||||||||||||||||||||||||||z‡u~~{ššœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFEPDIF@z‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†ŠNRQRHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuq}luwu•–œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDPEKJE€††††††††††††††††††††††††††††††††††††††††††††††††††††††††š™“NLFEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmyhrtt–˜œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDPELMJ†ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ}ACAAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuq|ktwx›žœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDODLML‰ƒ„‚†KNMNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv€mvyzž¡œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEOCJLKˆƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ’‘Œ‹KJEDHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvwmuwxœ œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENAGII†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŽŒ‡…DB=DL\-=EKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMGAAGKMKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKIe~••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••Ÿ’upnlioooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHK?KILBG—””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””•’ppnewkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHLXBF\9?š‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘—”glof~loooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHDP>E\>F›••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••œ˜prqgzioooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH=I=DYCL“““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““““—”wvsithoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHAJAFSDK~€~srqmpkoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJOIHLADbKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK^_jkmqmroooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHNPMIFACMHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFIfhjtjuoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIKIDFIFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBEnllvdtoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH?AGGCKQGJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJFJwqnw_qoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH@SJ?MK?HBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBRKI^=RQJHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHARIANKCPQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ=9;S4KMIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHBPHDQKH_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGPML^7HHDHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHDNGHSILo‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†€y~GLE@HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGLEKTEN~ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ™•ŽŽPPIEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKJDMS>Mˆ‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ƒ‚†JMLNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHNIBNQ8K‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰|€„FIJMHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHOHANP5I‘ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ’•’IFCFHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIGGJOUY‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡”ŽƒueVKEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIGFHMRUJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJLKIGDA?>HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIGFFIMOKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK9:=@DGJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKJGEEFHJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIITSSRQPPOHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKIHFEFFGBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBPOMJHECBHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIIHHGGGGNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNABCEHJKLHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIIJJIIEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFHKNQSUHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGHIJKKKKIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJIHFDCA@HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–ŸœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœŸ–Œ‡‰‹‹‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‡‹‹Š–žžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžžž–ŠŒ‹‡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ†‰Œ‹ŠŽ—Ÿ››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››››Ÿ—ŽŠ‹Œ‰†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„‡‹ŠŠŽ˜ šššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššš ˜ŽŠŠŠ‡„ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‚†‰‰‰Ž˜ œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ ˜Ž‰‰‰†‚ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠƒ†‰‰ˆ–žŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸŸž–ˆ‰‰‡ƒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ…ˆŠ‰‡‹“››“‹‡‰Šˆ…ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠˆŠŒ‰†ˆ˜••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••˜ˆ†‰ŒŠˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŒ‰†‡Ž••އ†‰ŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooopmnt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ˆˆ‡‡‡ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooomnpqrrqqllllllllllllllllllllllllroov……‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‰‰‰‰ˆˆŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooonoopppoonnnnnnnnnnnnnnnnnnnnnnnnommt~„„‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooopoonnmmmppppppppppppppppppppppppmjkr|ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‹‹‹ŒŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqpnllllmnnnnnnnnnnnnnnnnnnnnnnnnljjq{‚‚€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ˆˆ‰Š‹‹ŒŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqpnlkmnpmmmmmmmmmmmmmmmmmmmmmmmmpnns}ƒ‚€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†††††††††††††††††††††††††††††††††††††††††††††††††††††††††…†‡ˆ‰Š‹ŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooorpmlmosurrrrrrrrrrrrrrrrrrrrrrrrxutx€„ƒ€ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„ƒƒ„†‡‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqomlnsx{}}}}}}}}}}}}}}}}}}}}}}}}}z~„‡…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…†‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚‚ƒ„†‡‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠooooooooooooooooooooooooooooooooooooooooooooooooooooooooqommpu{†††††††††††††††††††††††††‚†ˆ…ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚„…‡‰Š‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooooooooooooooooooooooooooooooooooolnpnmr}†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ†ˆŠŒŒŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠpppppppppppppppppppppppppppppppppppppppppppppppppppppppprolknu}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…ˆŠ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ooooooooooooooooooooooooooooooooooooooooooooooooooooooooomjjnu}‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…‡‰Š‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹oooooooooooooooooooooooooooooooooooooooooooooooooooooooomkjjnu}‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ…†ˆ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠppppppppppppppppppppppppppppppppppppppppppppppppppppppppmlklpw~‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒƒ„…†‡‡ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuurqqruz‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„……††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††††{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{zzyy{~ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„……ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ„ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ„„„„„……………………………………………………………………………………………………………………………………………………‰ˆ‡……………ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚‚‚‚‚………………………………………………………………‚‡‹Š…ƒ†Šƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ~‚†…‚†ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚‚ƒ„„……††††††††††††††††††††††††††††††††††††††††††††††††††††††††{{{{{{{{{{{{{{{{{{{{{{{{w|~zy}‚ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚‚ƒ„…†‡‡ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆuuuuuuuuuuuuuuuuuuuuuuuuquxvssx}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ„…‡ˆ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠppppppppppppppppppppppppmpsqmnu{ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ„†ˆŠ‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹ooooooooooooooooooooooooknpnkmt|ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…‡‰‹Œ‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹oooooooooooooooooooooooolnomjmv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚ƒ…‡‰ŒŽŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠpppppppppppppppppppppppplopmknwƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ†ˆŠ‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠoooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ \ No newline at end of file diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp deleted file mode 100644 index 7c2d076992..0000000000 --- a/libs/ultrahdr/tests/gainmapmath_test.cpp +++ /dev/null @@ -1,1359 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include -#include - -namespace android::ultrahdr { - -class GainMapMathTest : public testing::Test { -public: - GainMapMathTest(); - ~GainMapMathTest(); - - float ComparisonEpsilon() { return 1e-4f; } - float LuminanceEpsilon() { return 1e-2f; } - float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); } - - Color Yuv420(uint8_t y, uint8_t u, uint8_t v) { - return {{{ static_cast(y) / 255.0f, - (static_cast(u) - 128.0f) / 255.0f, - (static_cast(v) - 128.0f) / 255.0f }}}; - } - - Color P010(uint16_t y, uint16_t u, uint16_t v) { - return {{{ (static_cast(y) - 64.0f) / 876.0f, - (static_cast(u) - 64.0f) / 896.0f - 0.5f, - (static_cast(v) - 64.0f) / 896.0f - 0.5f }}}; - } - - float Map(uint8_t e) { - return static_cast(e) / 255.0f; - } - - Color ColorMin(Color e1, Color e2) { - return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}}; - } - - Color ColorMax(Color e1, Color e2) { - return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}}; - } - - Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } - Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; } - - Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; } - Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; } - Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; } - - Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } - Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; } - - Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; } - Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; } - Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; } - - Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; } - Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; } - Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; } - - Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; } - Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; } - Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; } - - float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { - Color rgb_gamma = srgbYuvToRgb(yuv_gamma); - Color rgb = srgbInvOetf(rgb_gamma); - float luminance_scaled = luminanceFn(rgb); - return luminance_scaled * kSdrWhiteNits; - } - - float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { - Color rgb_gamma = p3YuvToRgb(yuv_gamma); - Color rgb = srgbInvOetf(rgb_gamma); - float luminance_scaled = luminanceFn(rgb); - return luminance_scaled * kSdrWhiteNits; - } - - float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf, - ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn, - float scale_factor) { - Color rgb_gamma = bt2100YuvToRgb(yuv_gamma); - Color rgb = hdrInvOetf(rgb_gamma); - rgb = gamutConversionFn(rgb); - float luminance_scaled = luminanceFn(rgb); - return luminance_scaled * scale_factor; - } - - Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) { - Color rgb_gamma = srgbYuvToRgb(yuv_gamma); - Color rgb = srgbInvOetf(rgb_gamma); - return applyGain(rgb, gain, metadata); - } - - jpegr_uncompressed_struct Yuv420Image() { - static uint8_t pixels[] = { - // Y - 0x00, 0x10, 0x20, 0x30, - 0x01, 0x11, 0x21, 0x31, - 0x02, 0x12, 0x22, 0x32, - 0x03, 0x13, 0x23, 0x33, - // U - 0xA0, 0xA1, - 0xA2, 0xA3, - // V - 0xB0, 0xB1, - 0xB2, 0xB3, - }; - return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 }; - } - - Color (*Yuv420Colors())[4] { - static Color colors[4][4] = { - { - Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0), - Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1), - }, { - Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0), - Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1), - }, { - Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2), - Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3), - }, { - Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2), - Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3), - }, - }; - return colors; - } - - jpegr_uncompressed_struct P010Image() { - static uint16_t pixels[] = { - // Y - 0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6, - 0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6, - 0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6, - 0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6, - // UV - 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6, - 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6, - }; - return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 }; - } - - Color (*P010Colors())[4] { - static Color colors[4][4] = { - { - P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0), - P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1), - }, { - P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0), - P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1), - }, { - P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2), - P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3), - }, { - P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2), - P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3), - }, - }; - return colors; - } - - jpegr_uncompressed_struct MapImage() { - static uint8_t pixels[] = { - 0x00, 0x10, 0x20, 0x30, - 0x01, 0x11, 0x21, 0x31, - 0x02, 0x12, 0x22, 0x32, - 0x03, 0x13, 0x23, 0x33, - }; - return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED }; - } - - float (*MapValues())[4] { - static float values[4][4] = { - { - Map(0x00), Map(0x10), Map(0x20), Map(0x30), - }, { - Map(0x01), Map(0x11), Map(0x21), Map(0x31), - }, { - Map(0x02), Map(0x12), Map(0x22), Map(0x32), - }, { - Map(0x03), Map(0x13), Map(0x23), Map(0x33), - }, - }; - return values; - } - -protected: - virtual void SetUp(); - virtual void TearDown(); -}; - -GainMapMathTest::GainMapMathTest() {} -GainMapMathTest::~GainMapMathTest() {} - -void GainMapMathTest::SetUp() {} -void GainMapMathTest::TearDown() {} - -#define EXPECT_RGB_EQ(e1, e2) \ - EXPECT_FLOAT_EQ((e1).r, (e2).r); \ - EXPECT_FLOAT_EQ((e1).g, (e2).g); \ - EXPECT_FLOAT_EQ((e1).b, (e2).b) - -#define EXPECT_RGB_NEAR(e1, e2) \ - EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \ - EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \ - EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon()) - -#define EXPECT_RGB_CLOSE(e1, e2) \ - EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \ - EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \ - EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f) - -#define EXPECT_YUV_EQ(e1, e2) \ - EXPECT_FLOAT_EQ((e1).y, (e2).y); \ - EXPECT_FLOAT_EQ((e1).u, (e2).u); \ - EXPECT_FLOAT_EQ((e1).v, (e2).v) - -#define EXPECT_YUV_NEAR(e1, e2) \ - EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \ - EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \ - EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon()) - -#define EXPECT_YUV_BETWEEN(e, min, max) \ - EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \ - EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \ - EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v))) - -// TODO: a bunch of these tests can be parameterized. - -TEST_F(GainMapMathTest, ColorConstruct) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - EXPECT_FLOAT_EQ(e1.r, 0.1f); - EXPECT_FLOAT_EQ(e1.g, 0.2f); - EXPECT_FLOAT_EQ(e1.b, 0.3f); - - EXPECT_FLOAT_EQ(e1.y, 0.1f); - EXPECT_FLOAT_EQ(e1.u, 0.2f); - EXPECT_FLOAT_EQ(e1.v, 0.3f); -} - -TEST_F(GainMapMathTest, ColorAddColor) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - Color e2 = e1 + e1; - EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); - EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f); - EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f); - - e2 += e1; - EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f); - EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f); - EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f); -} - -TEST_F(GainMapMathTest, ColorAddFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - Color e2 = e1 + 0.1f; - EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f); - EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f); - EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f); - - e2 += 0.1f; - EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f); - EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f); - EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f); -} - -TEST_F(GainMapMathTest, ColorSubtractColor) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - Color e2 = e1 - e1; - EXPECT_FLOAT_EQ(e2.r, 0.0f); - EXPECT_FLOAT_EQ(e2.g, 0.0f); - EXPECT_FLOAT_EQ(e2.b, 0.0f); - - e2 -= e1; - EXPECT_FLOAT_EQ(e2.r, -e1.r); - EXPECT_FLOAT_EQ(e2.g, -e1.g); - EXPECT_FLOAT_EQ(e2.b, -e1.b); -} - -TEST_F(GainMapMathTest, ColorSubtractFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - Color e2 = e1 - 0.1f; - EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f); - EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f); - EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f); - - e2 -= 0.1f; - EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f); - EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f); - EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f); -} - -TEST_F(GainMapMathTest, ColorMultiplyFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - Color e2 = e1 * 2.0f; - EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); - EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f); - EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f); - - e2 *= 2.0f; - EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f); - EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f); - EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f); -} - -TEST_F(GainMapMathTest, ColorDivideFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; - - Color e2 = e1 / 2.0f; - EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f); - EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f); - EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f); - - e2 /= 2.0f; - EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f); - EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f); - EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f); -} - -TEST_F(GainMapMathTest, SrgbLuminance) { - EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f); - EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f); -} - -TEST_F(GainMapMathTest, SrgbYuvToRgb) { - Color rgb_black = srgbYuvToRgb(YuvBlack()); - EXPECT_RGB_NEAR(rgb_black, RgbBlack()); - - Color rgb_white = srgbYuvToRgb(YuvWhite()); - EXPECT_RGB_NEAR(rgb_white, RgbWhite()); - - Color rgb_r = srgbYuvToRgb(SrgbYuvRed()); - EXPECT_RGB_NEAR(rgb_r, RgbRed()); - - Color rgb_g = srgbYuvToRgb(SrgbYuvGreen()); - EXPECT_RGB_NEAR(rgb_g, RgbGreen()); - - Color rgb_b = srgbYuvToRgb(SrgbYuvBlue()); - EXPECT_RGB_NEAR(rgb_b, RgbBlue()); -} - -TEST_F(GainMapMathTest, SrgbRgbToYuv) { - Color yuv_black = srgbRgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv_black, YuvBlack()); - - Color yuv_white = srgbRgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv_white, YuvWhite()); - - Color yuv_r = srgbRgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed()); - - Color yuv_g = srgbRgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen()); - - Color yuv_b = srgbRgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue()); -} - -TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) { - Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack())); - EXPECT_RGB_NEAR(rgb_black, RgbBlack()); - - Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite())); - EXPECT_RGB_NEAR(rgb_white, RgbWhite()); - - Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed())); - EXPECT_RGB_NEAR(rgb_r, RgbRed()); - - Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen())); - EXPECT_RGB_NEAR(rgb_g, RgbGreen()); - - Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue())); - EXPECT_RGB_NEAR(rgb_b, RgbBlue()); -} - -TEST_F(GainMapMathTest, SrgbTransferFunction) { - EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f); - EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon()); - EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon()); - EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f); -} - -TEST_F(GainMapMathTest, P3Luminance) { - EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f); - EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f); - EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f); - EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f); - EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f); -} - -TEST_F(GainMapMathTest, P3YuvToRgb) { - Color rgb_black = p3YuvToRgb(YuvBlack()); - EXPECT_RGB_NEAR(rgb_black, RgbBlack()); - - Color rgb_white = p3YuvToRgb(YuvWhite()); - EXPECT_RGB_NEAR(rgb_white, RgbWhite()); - - Color rgb_r = p3YuvToRgb(P3YuvRed()); - EXPECT_RGB_NEAR(rgb_r, RgbRed()); - - Color rgb_g = p3YuvToRgb(P3YuvGreen()); - EXPECT_RGB_NEAR(rgb_g, RgbGreen()); - - Color rgb_b = p3YuvToRgb(P3YuvBlue()); - EXPECT_RGB_NEAR(rgb_b, RgbBlue()); -} - -TEST_F(GainMapMathTest, P3RgbToYuv) { - Color yuv_black = p3RgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv_black, YuvBlack()); - - Color yuv_white = p3RgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv_white, YuvWhite()); - - Color yuv_r = p3RgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv_r, P3YuvRed()); - - Color yuv_g = p3RgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv_g, P3YuvGreen()); - - Color yuv_b = p3RgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv_b, P3YuvBlue()); -} - -TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) { - Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack())); - EXPECT_RGB_NEAR(rgb_black, RgbBlack()); - - Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite())); - EXPECT_RGB_NEAR(rgb_white, RgbWhite()); - - Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed())); - EXPECT_RGB_NEAR(rgb_r, RgbRed()); - - Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen())); - EXPECT_RGB_NEAR(rgb_g, RgbGreen()); - - Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue())); - EXPECT_RGB_NEAR(rgb_b, RgbBlue()); -} -TEST_F(GainMapMathTest, Bt2100Luminance) { - EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f); - EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f); - EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f); - EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f); - EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f); -} - -TEST_F(GainMapMathTest, Bt2100YuvToRgb) { - Color rgb_black = bt2100YuvToRgb(YuvBlack()); - EXPECT_RGB_NEAR(rgb_black, RgbBlack()); - - Color rgb_white = bt2100YuvToRgb(YuvWhite()); - EXPECT_RGB_NEAR(rgb_white, RgbWhite()); - - Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed()); - EXPECT_RGB_NEAR(rgb_r, RgbRed()); - - Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen()); - EXPECT_RGB_NEAR(rgb_g, RgbGreen()); - - Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue()); - EXPECT_RGB_NEAR(rgb_b, RgbBlue()); -} - -TEST_F(GainMapMathTest, Bt2100RgbToYuv) { - Color yuv_black = bt2100RgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv_black, YuvBlack()); - - Color yuv_white = bt2100RgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv_white, YuvWhite()); - - Color yuv_r = bt2100RgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed()); - - Color yuv_g = bt2100RgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen()); - - Color yuv_b = bt2100RgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue()); -} - -TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) { - Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack())); - EXPECT_RGB_NEAR(rgb_black, RgbBlack()); - - Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite())); - EXPECT_RGB_NEAR(rgb_white, RgbWhite()); - - Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed())); - EXPECT_RGB_NEAR(rgb_r, RgbRed()); - - Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen())); - EXPECT_RGB_NEAR(rgb_g, RgbGreen()); - - Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue())); - EXPECT_RGB_NEAR(rgb_b, RgbBlue()); -} - -TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) { - Color yuv_black = srgbRgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack()); - - Color yuv_white = srgbRgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite()); - - Color yuv_r = srgbRgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed()); - - Color yuv_g = srgbRgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen()); - - Color yuv_b = srgbRgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue()); -} - -TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) { - Color yuv_black = srgbRgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack()); - - Color yuv_white = srgbRgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite()); - - Color yuv_r = srgbRgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed()); - - Color yuv_g = srgbRgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen()); - - Color yuv_b = srgbRgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue()); -} - -TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) { - Color yuv_black = p3RgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack()); - - Color yuv_white = p3RgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite()); - - Color yuv_r = p3RgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed()); - - Color yuv_g = p3RgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen()); - - Color yuv_b = p3RgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue()); -} - -TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) { - Color yuv_black = p3RgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack()); - - Color yuv_white = p3RgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite()); - - Color yuv_r = p3RgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed()); - - Color yuv_g = p3RgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen()); - - Color yuv_b = p3RgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue()); -} - -TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) { - Color yuv_black = bt2100RgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack()); - - Color yuv_white = bt2100RgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite()); - - Color yuv_r = bt2100RgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed()); - - Color yuv_g = bt2100RgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen()); - - Color yuv_b = bt2100RgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue()); -} - -TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) { - Color yuv_black = bt2100RgbToYuv(RgbBlack()); - EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack()); - - Color yuv_white = bt2100RgbToYuv(RgbWhite()); - EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite()); - - Color yuv_r = bt2100RgbToYuv(RgbRed()); - EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed()); - - Color yuv_g = bt2100RgbToYuv(RgbGreen()); - EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen()); - - Color yuv_b = bt2100RgbToYuv(RgbBlue()); - EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue()); -} - -TEST_F(GainMapMathTest, TransformYuv420) { - ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100, - yuv2100To709, yuv2100To601 }; - for (const ColorTransformFn& transform : transforms) { - jpegr_uncompressed_struct input = Yuv420Image(); - - size_t out_buf_size = input.width * input.height * 3 / 2; - std::unique_ptr out_buf = std::make_unique(out_buf_size); - memcpy(out_buf.get(), input.data, out_buf_size); - jpegr_uncompressed_struct output = Yuv420Image(); - output.data = out_buf.get(); - output.chroma_data = out_buf.get() + input.width * input.height; - output.luma_stride = input.width; - output.chroma_stride = input.width / 2; - - transformYuv420(&output, 1, 1, transform); - - for (size_t y = 0; y < 4; ++y) { - for (size_t x = 0; x < 4; ++x) { - // Skip the last chroma sample, which we modified above - if (x >= 2 && y >= 2) { - continue; - } - - // All other pixels should remain unchanged - EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y)); - } - } - - // modified pixels should be updated as intended by the transformYuv420 algorithm - Color in1 = getYuv420Pixel(&input, 2, 2); - Color in2 = getYuv420Pixel(&input, 3, 2); - Color in3 = getYuv420Pixel(&input, 2, 3); - Color in4 = getYuv420Pixel(&input, 3, 3); - Color out1 = getYuv420Pixel(&output, 2, 2); - Color out2 = getYuv420Pixel(&output, 3, 2); - Color out3 = getYuv420Pixel(&output, 2, 3); - Color out4 = getYuv420Pixel(&output, 3, 3); - - EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon()); - EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon()); - EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon()); - EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon()); - - Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f; - - EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon()); - EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon()); - EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon()); - EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon()); - - EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon()); - EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon()); - EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon()); - EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon()); - } -} - -TEST_F(GainMapMathTest, HlgOetf) { - EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f); - EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon()); - EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon()); - EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f); - - Color e = {{{ 0.04167f, 0.08333f, 0.5f }}}; - Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}}; - EXPECT_RGB_NEAR(hlgOetf(e), e_gamma); -} - -TEST_F(GainMapMathTest, HlgInvOetf) { - EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f); - EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon()); - EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon()); - EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f); - - Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}}; - Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}}; - EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e); -} - -TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) { - EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f); - EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon()); - EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon()); - EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f); -} - -TEST_F(GainMapMathTest, PqOetf) { - EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f); - EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon()); - EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon()); - EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f); - - Color e = {{{ 0.01f, 0.5f, 0.99f }}}; - Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}}; - EXPECT_RGB_NEAR(pqOetf(e), e_gamma); -} - -TEST_F(GainMapMathTest, PqInvOetf) { - EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f); - EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon()); - EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon()); - EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f); - - Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}}; - Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}}; - EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e); -} - -TEST_F(GainMapMathTest, PqInvOetfLUT) { - for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kPqInvOETFNumEntries - 1); - EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value)); - } -} - -TEST_F(GainMapMathTest, HlgInvOetfLUT) { - for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kHlgInvOETFNumEntries - 1); - EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value)); - } -} - -TEST_F(GainMapMathTest, pqOetfLUT) { - for (int idx = 0; idx < kPqOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kPqOETFNumEntries - 1); - EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value)); - } -} - -TEST_F(GainMapMathTest, hlgOetfLUT) { - for (int idx = 0; idx < kHlgOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kHlgOETFNumEntries - 1); - EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value)); - } -} - -TEST_F(GainMapMathTest, srgbInvOetfLUT) { - for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kSrgbInvOETFNumEntries - 1); - EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value)); - } -} - -TEST_F(GainMapMathTest, applyGainLUT) { - for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), - .minContentBoost = 1.0f / static_cast(boost) }; - GainLUT gainLUT(&metadata); - GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); - for (int idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), - applyGainLUT(RgbBlack(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), - applyGainLUT(RgbWhite(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), - applyGainLUT(RgbRed(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), - applyGainLUT(RgbGreen(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), - applyGainLUT(RgbBlue(), value, gainLUT)); - EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), - applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), - applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), - applyGainLUT(RgbRed(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), - applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), - applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); - } - } - - for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), - .minContentBoost = 1.0f }; - GainLUT gainLUT(&metadata); - GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); - for (int idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), - applyGainLUT(RgbBlack(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), - applyGainLUT(RgbWhite(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), - applyGainLUT(RgbRed(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), - applyGainLUT(RgbGreen(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), - applyGainLUT(RgbBlue(), value, gainLUT)); - EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), - applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), - applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), - applyGainLUT(RgbRed(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), - applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), - applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); - } - } - - for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast(boost), - .minContentBoost = 1.0f / pow(static_cast(boost), - 1.0f / 3.0f) }; - GainLUT gainLUT(&metadata); - GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); - for (int idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), - applyGainLUT(RgbBlack(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), value, &metadata), - applyGainLUT(RgbWhite(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbRed(), value, &metadata), - applyGainLUT(RgbRed(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbGreen(), value, &metadata), - applyGainLUT(RgbGreen(), value, gainLUT)); - EXPECT_RGB_NEAR(applyGain(RgbBlue(), value, &metadata), - applyGainLUT(RgbBlue(), value, gainLUT)); - EXPECT_RGB_EQ(applyGainLUT(RgbBlack(), value, gainLUT), - applyGainLUT(RgbBlack(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbWhite(), value, gainLUT), - applyGainLUT(RgbWhite(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbRed(), value, gainLUT), - applyGainLUT(RgbRed(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbGreen(), value, gainLUT), - applyGainLUT(RgbGreen(), value, gainLUTWithBoost)); - EXPECT_RGB_EQ(applyGainLUT(RgbBlue(), value, gainLUT), - applyGainLUT(RgbBlue(), value, gainLUTWithBoost)); - } - } -} - -TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) { - EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f); - EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon()); - EXPECT_NEAR(pqInvOetf(pqOetf(0.5f)), 0.5f, ComparisonEpsilon()); - EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon()); - EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f); -} - -TEST_F(GainMapMathTest, ColorConversionLookup) { - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709), - identityConversion); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3), - p3ToBt709); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100), - bt2100ToBt709); - - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709), - bt709ToP3); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3), - identityConversion); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100), - bt2100ToP3); - - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709), - bt709ToBt2100); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3), - p3ToBt2100); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100), - identityConversion); - - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100), - nullptr); -} - -TEST_F(GainMapMathTest, EncodeGain) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; - - EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); - EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127); - EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeGain(0.5f, 0.0f, &metadata), 0); - - EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 127); - EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 255); - EXPECT_EQ(encodeGain(1.0f, 5.0f, &metadata), 255); - EXPECT_EQ(encodeGain(4.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeGain(4.0f, 0.5f, &metadata), 0); - EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191); - EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63); - - metadata.maxContentBoost = 2.0f; - metadata.minContentBoost = 1.0f / 2.0f; - - EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255); - EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191); - EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f / 8.0f; - - EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); - EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191); - EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f; - - EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0); - EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); - - EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 0); - EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); - EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170); - EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f; - - EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63); - EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); - - EXPECT_EQ(encodeGain(1.0f, 1.0f, &metadata), 63); - EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); - EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 191); - EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 127); - EXPECT_EQ(encodeGain(1.0f, 0.7071f, &metadata), 31); - EXPECT_EQ(encodeGain(1.0f, 0.5f, &metadata), 0); -} - -TEST_F(GainMapMathTest, ApplyGain) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; - float displayBoost = metadata.maxContentBoost; - - EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); - EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack()); - EXPECT_RGB_NEAR(applyGain(RgbBlack(), 1.0f, &metadata), RgbBlack()); - - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 4.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); - - metadata.maxContentBoost = 2.0f; - metadata.minContentBoost = 1.0f / 2.0f; - - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f / 8.0f; - - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f; - - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f; - - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite()); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f); - EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - - Color e = {{{ 0.0f, 0.5f, 1.0f }}}; - metadata.maxContentBoost = 4.0f; - metadata.minContentBoost = 1.0f / 4.0f; - - EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f); - EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f); - EXPECT_RGB_NEAR(applyGain(e, 0.5f, &metadata), e); - EXPECT_RGB_NEAR(applyGain(e, 0.75f, &metadata), e * 2.0f); - EXPECT_RGB_NEAR(applyGain(e, 1.0f, &metadata), e * 4.0f); - - EXPECT_RGB_EQ(applyGain(RgbBlack(), 1.0f, &metadata), - applyGain(RgbBlack(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyGain(RgbWhite(), 1.0f, &metadata), - applyGain(RgbWhite(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyGain(RgbRed(), 1.0f, &metadata), - applyGain(RgbRed(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyGain(RgbGreen(), 1.0f, &metadata), - applyGain(RgbGreen(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata), - applyGain(RgbBlue(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata), - applyGain(e, 1.0f, &metadata, displayBoost)); -} - -TEST_F(GainMapMathTest, GetYuv420Pixel) { - jpegr_uncompressed_struct image = Yuv420Image(); - Color (*colors)[4] = Yuv420Colors(); - - for (size_t y = 0; y < 4; ++y) { - for (size_t x = 0; x < 4; ++x) { - EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]); - } - } -} - -TEST_F(GainMapMathTest, GetP010Pixel) { - jpegr_uncompressed_struct image = P010Image(); - Color (*colors)[4] = P010Colors(); - - for (size_t y = 0; y < 4; ++y) { - for (size_t x = 0; x < 4; ++x) { - EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]); - } - } -} - -TEST_F(GainMapMathTest, SampleYuv420) { - jpegr_uncompressed_struct image = Yuv420Image(); - Color (*colors)[4] = Yuv420Colors(); - - static const size_t kMapScaleFactor = 2; - for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { - for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { - Color min = {{{ 1.0f, 1.0f, 1.0f }}}; - Color max = {{{ -1.0f, -1.0f, -1.0f }}}; - - for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { - for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { - Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx]; - min = ColorMin(min, e); - max = ColorMax(max, e); - } - } - - // Instead of reimplementing the sampling algorithm, confirm that the - // sample output is within the range of the min and max of the nearest - // points. - EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max); - } - } -} - -TEST_F(GainMapMathTest, SampleP010) { - jpegr_uncompressed_struct image = P010Image(); - Color (*colors)[4] = P010Colors(); - - static const size_t kMapScaleFactor = 2; - for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { - for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { - Color min = {{{ 1.0f, 1.0f, 1.0f }}}; - Color max = {{{ -1.0f, -1.0f, -1.0f }}}; - - for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { - for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { - Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx]; - min = ColorMin(min, e); - max = ColorMax(max, e); - } - } - - // Instead of reimplementing the sampling algorithm, confirm that the - // sample output is within the range of the min and max of the nearest - // points. - EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max); - } - } -} - -TEST_F(GainMapMathTest, SampleMap) { - jpegr_uncompressed_struct image = MapImage(); - float (*values)[4] = MapValues(); - - static const size_t kMapScaleFactor = 2; - ShepardsIDW idwTable(kMapScaleFactor); - for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) { - for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) { - size_t x_base = x / kMapScaleFactor; - size_t y_base = y / kMapScaleFactor; - - float min = 1.0f; - float max = -1.0f; - - min = fmin(min, values[y_base][x_base]); - max = fmax(max, values[y_base][x_base]); - if (y_base + 1 < 4) { - min = fmin(min, values[y_base + 1][x_base]); - max = fmax(max, values[y_base + 1][x_base]); - } - if (x_base + 1 < 4) { - min = fmin(min, values[y_base][x_base + 1]); - max = fmax(max, values[y_base][x_base + 1]); - } - if (y_base + 1 < 4 && x_base + 1 < 4) { - min = fmin(min, values[y_base + 1][x_base + 1]); - max = fmax(max, values[y_base + 1][x_base + 1]); - } - - // Instead of reimplementing the sampling algorithm, confirm that the - // sample output is within the range of the min and max of the nearest - // points. - EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y), - testing::AllOf(testing::Ge(min), testing::Le(max))); - EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable), - sampleMap(&image, kMapScaleFactor, x, y)); - } - } -} - -TEST_F(GainMapMathTest, ColorToRgba1010102) { - EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30); - EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF); - EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff); - EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10); - EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20); - - Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; - EXPECT_EQ(colorToRgba1010102(e_gamma), - 0x3 << 30 - | static_cast(0.1f * static_cast(0x3ff)) - | static_cast(0.2f * static_cast(0x3ff)) << 10 - | static_cast(0.3f * static_cast(0x3ff)) << 20); -} - -TEST_F(GainMapMathTest, ColorToRgbaF16) { - EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48); - EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00); - EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00)); - EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16)); - EXPECT_EQ(colorToRgbaF16(RgbBlue()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32)); - - Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; - EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66); -} - -TEST_F(GainMapMathTest, Float32ToFloat16) { - EXPECT_EQ(floatToHalf(0.1f), 0x2E66); - EXPECT_EQ(floatToHalf(0.0f), 0x0); - EXPECT_EQ(floatToHalf(1.0f), 0x3C00); - EXPECT_EQ(floatToHalf(-1.0f), 0xBC00); - EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF); // float max - EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF); // float min - EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero -} - -TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) { - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), - 0.0f); - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), - kSdrWhiteNits); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance), - srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance), - srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance), - srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); -} - -TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) { - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), - 0.0f); - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), - kSdrWhiteNits); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance), - p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance), - p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance), - p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); -} - -TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) { - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), - 0.0f); - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), - kSdrWhiteNits); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance), - bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance), - bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance), - bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon()); -} - -TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) { - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), - 0.0f); - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), - kHlgMaxNits); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), - bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), - bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), - bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon()); -} - -TEST_F(GainMapMathTest, GenerateMapLuminancePq) { - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - 0.0f); - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - kPqMaxNits); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon()); -} - -TEST_F(GainMapMathTest, ApplyMap) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f, - .minContentBoost = 1.0f / 8.0f }; - - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), - RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata), - RgbRed() * 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata), - RgbGreen() * 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata), - RgbBlue() * 8.0f); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata), - RgbWhite() * sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata), - RgbRed() * sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata), - RgbGreen() * sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata), - RgbBlue() * sqrt(8.0f)); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), - RgbWhite()); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata), - RgbRed()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata), - RgbGreen()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata), - RgbBlue()); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), - RgbWhite() / sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata), - RgbRed() / sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata), - RgbGreen() / sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata), - RgbBlue() / sqrt(8.0f)); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), - RgbWhite() / 8.0f); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata), - RgbRed() / 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), - RgbGreen() / 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), - RgbBlue() / 8.0f); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f; - - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), - RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), - RgbWhite() * 4.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), - RgbWhite() * 2.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), - RgbWhite()); - - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f;; - - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), - RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), - RgbWhite() * 4.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), - RgbWhite() * 2.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), - RgbWhite()); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), - RgbWhite() / 2.0f); -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/tests/icchelper_test.cpp b/libs/ultrahdr/tests/icchelper_test.cpp deleted file mode 100644 index ff61c08574..0000000000 --- a/libs/ultrahdr/tests/icchelper_test.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include -#include - -namespace android::ultrahdr { - -class IccHelperTest : public testing::Test { -public: - IccHelperTest(); - ~IccHelperTest(); -protected: - virtual void SetUp(); - virtual void TearDown(); -}; - -IccHelperTest::IccHelperTest() {} - -IccHelperTest::~IccHelperTest() {} - -void IccHelperTest::SetUp() {} - -void IccHelperTest::TearDown() {} - -TEST_F(IccHelperTest, iccWriteThenRead) { - sp iccBt709 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, - ULTRAHDR_COLORGAMUT_BT709); - ASSERT_NE(iccBt709->getLength(), 0); - ASSERT_NE(iccBt709->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()), - ULTRAHDR_COLORGAMUT_BT709); - - sp iccP3 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3); - ASSERT_NE(iccP3->getLength(), 0); - ASSERT_NE(iccP3->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()), - ULTRAHDR_COLORGAMUT_P3); - - sp iccBt2100 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, - ULTRAHDR_COLORGAMUT_BT2100); - ASSERT_NE(iccBt2100->getLength(), 0); - ASSERT_NE(iccBt2100->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()), - ULTRAHDR_COLORGAMUT_BT2100); -} - -TEST_F(IccHelperTest, iccEndianness) { - sp icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); - size_t profile_size = icc->getLength() - kICCIdentifierSize; - - uint8_t* icc_bytes = reinterpret_cast(icc->getData()) + kICCIdentifierSize; - uint32_t encoded_size = static_cast(icc_bytes[0]) << 24 | - static_cast(icc_bytes[1]) << 16 | - static_cast(icc_bytes[2]) << 8 | - static_cast(icc_bytes[3]); - - EXPECT_EQ(static_cast(encoded_size), profile_size); -} - -} // namespace android::ultrahdr - diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp deleted file mode 100644 index af0d59edc0..0000000000 --- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include -#include - -#include - -namespace android::ultrahdr { - -// No ICC or EXIF -#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg" -#define YUV_IMAGE_SIZE 20193 -// Has ICC and EXIF -#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg" -#define YUV_ICC_IMAGE_SIZE 34266 -// No ICC or EXIF -#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg" -#define GREY_IMAGE_SIZE 20193 - -#define IMAGE_WIDTH 320 -#define IMAGE_HEIGHT 240 - -class JpegDecoderHelperTest : public testing::Test { -public: - struct Image { - std::unique_ptr buffer; - size_t size; - }; - JpegDecoderHelperTest(); - ~JpegDecoderHelperTest(); - -protected: - virtual void SetUp(); - virtual void TearDown(); - - Image mYuvImage, mYuvIccImage, mGreyImage; -}; - -JpegDecoderHelperTest::JpegDecoderHelperTest() {} - -JpegDecoderHelperTest::~JpegDecoderHelperTest() {} - -static size_t getFileSize(int fd) { - struct stat st; - if (fstat(fd, &st) < 0) { - ALOGW("%s : fstat failed", __func__); - return 0; - } - return st.st_size; // bytes -} - -static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) { - int fd = open(filename, O_CLOEXEC); - if (fd < 0) { - return false; - } - int length = getFileSize(fd); - if (length == 0) { - close(fd); - return false; - } - result->buffer.reset(new uint8_t[length]); - if (read(fd, result->buffer.get(), length) != static_cast(length)) { - close(fd); - return false; - } - close(fd); - return true; -} - -void JpegDecoderHelperTest::SetUp() { - if (!loadFile(YUV_IMAGE, &mYuvImage)) { - FAIL() << "Load file " << YUV_IMAGE << " failed"; - } - mYuvImage.size = YUV_IMAGE_SIZE; - if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) { - FAIL() << "Load file " << YUV_ICC_IMAGE << " failed"; - } - mYuvIccImage.size = YUV_ICC_IMAGE_SIZE; - if (!loadFile(GREY_IMAGE, &mGreyImage)) { - FAIL() << "Load file " << GREY_IMAGE << " failed"; - } - mGreyImage.size = GREY_IMAGE_SIZE; -} - -void JpegDecoderHelperTest::TearDown() {} - -TEST_F(JpegDecoderHelperTest, decodeYuvImage) { - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); - ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); - EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_UNSPECIFIED); -} - -TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) { - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size)); - ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); - EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_BT709); -} - -TEST_F(JpegDecoderHelperTest, decodeGreyImage) { - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size)); - ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); -} - -TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) { - size_t width = 0, height = 0; - std::vector icc, exif; - - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width, - &height, &icc, &exif)); - - EXPECT_EQ(width, IMAGE_WIDTH); - EXPECT_EQ(height, IMAGE_HEIGHT); - EXPECT_EQ(icc.size(), 0); - EXPECT_EQ(exif.size(), 0); -} - -TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) { - size_t width = 0, height = 0; - std::vector icc, exif; - - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size, - &width, &height, &icc, &exif)); - - EXPECT_EQ(width, IMAGE_WIDTH); - EXPECT_EQ(height, IMAGE_HEIGHT); - EXPECT_GT(icc.size(), 0); - EXPECT_GT(exif.size(), 0); - - EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709); -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp deleted file mode 100644 index af54eb2a8a..0000000000 --- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include - -#include - -namespace android::ultrahdr { - -#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12" -#define ALIGNED_IMAGE_WIDTH 320 -#define ALIGNED_IMAGE_HEIGHT 240 -#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y" -#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH -#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT -#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12" -#define UNALIGNED_IMAGE_WIDTH 318 -#define UNALIGNED_IMAGE_HEIGHT 240 -#define JPEG_QUALITY 90 - -class JpegEncoderHelperTest : public testing::Test { -public: - struct Image { - std::unique_ptr buffer; - size_t width; - size_t height; - }; - JpegEncoderHelperTest(); - ~JpegEncoderHelperTest(); - -protected: - virtual void SetUp(); - virtual void TearDown(); - - Image mAlignedImage, mUnalignedImage, mSingleChannelImage; -}; - -JpegEncoderHelperTest::JpegEncoderHelperTest() {} - -JpegEncoderHelperTest::~JpegEncoderHelperTest() {} - -static size_t getFileSize(int fd) { - struct stat st; - if (fstat(fd, &st) < 0) { - ALOGW("%s : fstat failed", __func__); - return 0; - } - return st.st_size; // bytes -} - -static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) { - int fd = open(filename, O_CLOEXEC); - if (fd < 0) { - return false; - } - int length = getFileSize(fd); - if (length == 0) { - close(fd); - return false; - } - result->buffer.reset(new uint8_t[length]); - if (read(fd, result->buffer.get(), length) != static_cast(length)) { - close(fd); - return false; - } - close(fd); - return true; -} - -void JpegEncoderHelperTest::SetUp() { - if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) { - FAIL() << "Load file " << ALIGNED_IMAGE << " failed"; - } - mAlignedImage.width = ALIGNED_IMAGE_WIDTH; - mAlignedImage.height = ALIGNED_IMAGE_HEIGHT; - if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) { - FAIL() << "Load file " << UNALIGNED_IMAGE << " failed"; - } - mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH; - mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT; - if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) { - FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed"; - } - mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH; - mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT; -} - -void JpegEncoderHelperTest::TearDown() {} - -TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { - JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), - mAlignedImage.buffer.get() + - mAlignedImage.width * mAlignedImage.height, - mAlignedImage.width, mAlignedImage.height, - mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY, - NULL, 0)); - ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); -} - -TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { - JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(), - mUnalignedImage.buffer.get() + - mUnalignedImage.width * mUnalignedImage.height, - mUnalignedImage.width, mUnalignedImage.height, - mUnalignedImage.width, mUnalignedImage.width / 2, - JPEG_QUALITY, NULL, 0)); - ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); -} - -TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) { - JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr, - mSingleChannelImage.width, mSingleChannelImage.height, - mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0)); - ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); -} - -} // namespace android::ultrahdr diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp deleted file mode 100644 index 5fa758e88d..0000000000 --- a/libs/ultrahdr/tests/jpegr_test.cpp +++ /dev/null @@ -1,2035 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include -#include -#include - -#include -#include -#include - -#include -#include - -//#define DUMP_OUTPUT - -namespace android::ultrahdr { - -// resources used by unit tests -const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010"; -const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420"; -const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg"; -const int kImageWidth = 1280; -const int kImageHeight = 720; -const int kQuality = 90; - -// Wrapper to describe the input type -typedef enum { - YCbCr_p010 = 0, - YCbCr_420 = 1, -} UhdrInputFormat; - -/** - * Wrapper class for raw resource - * Sample usage: - * UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010); - * rawImg.setImageColorGamut(colorGamut)); - * rawImg.setImageStride(strideLuma, strideChroma); // optional - * rawImg.setChromaMode(false); // optional - * rawImg.allocateMemory(); - * rawImg.loadRawResource(kYCbCrP010FileName); - */ -class UhdrUnCompressedStructWrapper { -public: - UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format); - ~UhdrUnCompressedStructWrapper() = default; - - bool setChromaMode(bool isChromaContiguous); - bool setImageStride(int lumaStride, int chromaStride); - bool setImageColorGamut(ultrahdr_color_gamut colorGamut); - bool allocateMemory(); - bool loadRawResource(const char* fileName); - jr_uncompressed_ptr getImageHandle(); - -private: - std::unique_ptr mLumaData; - std::unique_ptr mChromaData; - jpegr_uncompressed_struct mImg; - UhdrInputFormat mFormat; - bool mIsChromaContiguous; -}; - -/** - * Wrapper class for compressed resource - * Sample usage: - * UhdrCompressedStructWrapper jpgImg(width, height); - * rawImg.allocateMemory(); - */ -class UhdrCompressedStructWrapper { -public: - UhdrCompressedStructWrapper(uint32_t width, uint32_t height); - ~UhdrCompressedStructWrapper() = default; - - bool allocateMemory(); - jr_compressed_ptr getImageHandle(); - -private: - std::unique_ptr mData; - jpegr_compressed_struct mImg{}; - uint32_t mWidth; - uint32_t mHeight; -}; - -UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, - UhdrInputFormat format) { - mImg.data = nullptr; - mImg.width = width; - mImg.height = height; - mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - mImg.chroma_data = nullptr; - mImg.luma_stride = 0; - mImg.chroma_stride = 0; - mFormat = format; - mIsChromaContiguous = true; -} - -bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) { - if (mLumaData.get() != nullptr) { - std::cerr << "Object has sailed, no further modifications are allowed" << std::endl; - return false; - } - mIsChromaContiguous = isChromaContiguous; - return true; -} - -bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) { - if (mLumaData.get() != nullptr) { - std::cerr << "Object has sailed, no further modifications are allowed" << std::endl; - return false; - } - if (lumaStride != 0) { - if (lumaStride < mImg.width) { - std::cerr << "Bad luma stride received" << std::endl; - return false; - } - mImg.luma_stride = lumaStride; - } - if (chromaStride != 0) { - if (mFormat == YCbCr_p010 && chromaStride < mImg.width) { - std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl; - return false; - } - if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) { - std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl; - return false; - } - mImg.chroma_stride = chromaStride; - } - return true; -} - -bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) { - if (mLumaData.get() != nullptr) { - std::cerr << "Object has sailed, no further modifications are allowed" << std::endl; - return false; - } - mImg.colorGamut = colorGamut; - return true; -} - -bool UhdrUnCompressedStructWrapper::allocateMemory() { - if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) || - (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) { - std::cerr << "Object in bad state, mem alloc failed" << std::endl; - return false; - } - int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride; - int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1); - int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1); - if (mIsChromaContiguous) { - chromaSize *= lumaStride; - } else { - if (mImg.chroma_stride == 0) { - std::cerr << "Object in bad state, mem alloc failed" << std::endl; - return false; - } - if (mFormat == YCbCr_p010) { - chromaSize *= mImg.chroma_stride; - } else { - chromaSize *= (mImg.chroma_stride * 2); - } - } - if (mIsChromaContiguous) { - mLumaData = std::make_unique(lumaSize + chromaSize); - mImg.data = mLumaData.get(); - mImg.chroma_data = nullptr; - } else { - mLumaData = std::make_unique(lumaSize); - mImg.data = mLumaData.get(); - mChromaData = std::make_unique(chromaSize); - mImg.chroma_data = mChromaData.get(); - } - return true; -} - -bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) { - if (!mImg.data) { - std::cerr << "memory is not allocated, read not possible" << std::endl; - return false; - } - std::ifstream ifd(fileName, std::ios::binary | std::ios::ate); - if (ifd.good()) { - int bpp = mFormat == YCbCr_p010 ? 2 : 1; - int size = ifd.tellg(); - int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling - if (size < length) { - std::cerr << "requested to read " << length << " bytes from file : " << fileName - << ", file contains only " << length << " bytes" << std::endl; - return false; - } - ifd.seekg(0, std::ios::beg); - int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride; - char* mem = static_cast(mImg.data); - for (int i = 0; i < mImg.height; i++) { - ifd.read(mem, mImg.width * bpp); - mem += lumaStride * bpp; - } - if (!mIsChromaContiguous) { - mem = static_cast(mImg.chroma_data); - } - int chromaStride; - if (mIsChromaContiguous) { - chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2; - } else { - if (mFormat == YCbCr_p010) { - chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride; - } else { - chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride; - } - } - if (mFormat == YCbCr_p010) { - for (int i = 0; i < mImg.height / 2; i++) { - ifd.read(mem, mImg.width * 2); - mem += chromaStride * 2; - } - } else { - for (int i = 0; i < mImg.height / 2; i++) { - ifd.read(mem, (mImg.width / 2)); - mem += chromaStride; - } - for (int i = 0; i < mImg.height / 2; i++) { - ifd.read(mem, (mImg.width / 2)); - mem += chromaStride; - } - } - return true; - } - std::cerr << "unable to open file : " << fileName << std::endl; - return false; -} - -jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() { - return &mImg; -} - -UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) { - mWidth = width; - mHeight = height; -} - -bool UhdrCompressedStructWrapper::allocateMemory() { - if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) { - std::cerr << "Object in bad state, mem alloc failed" << std::endl; - return false; - } - int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2)); - mData = std::make_unique(maxLength); - mImg.data = mData.get(); - mImg.length = 0; - mImg.maxLength = maxLength; - return true; -} - -jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() { - return &mImg; -} - -static bool writeFile(const char* filename, void*& result, int length) { - std::ofstream ofd(filename, std::ios::binary); - if (ofd.is_open()) { - ofd.write(static_cast(result), length); - return true; - } - std::cerr << "unable to write to file : " << filename << std::endl; - return false; -} - -static bool readFile(const char* fileName, void*& result, int maxLength, int& length) { - std::ifstream ifd(fileName, std::ios::binary | std::ios::ate); - if (ifd.good()) { - length = ifd.tellg(); - if (length > maxLength) { - std::cerr << "not enough space to read file" << std::endl; - return false; - } - ifd.seekg(0, std::ios::beg); - ifd.read(static_cast(result), length); - return true; - } - std::cerr << "unable to read file : " << fileName << std::endl; - return false; -} - -void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) { - std::vector iccData(0); - std::vector exifData(0); - jpegr_info_struct info{0, 0, &iccData, &exifData}; - JpegR jpegHdr; - ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info)); - ASSERT_EQ(kImageWidth, info.width); - ASSERT_EQ(kImageHeight, info.height); - size_t outSize = info.width * info.height * 8; - std::unique_ptr data = std::make_unique(outSize); - jpegr_uncompressed_struct destImage{}; - destImage.data = data.get(); - ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage)); - ASSERT_EQ(kImageWidth, destImage.width); - ASSERT_EQ(kImageHeight, destImage.height); -#ifdef DUMP_OUTPUT - if (!writeFile(outFileName, destImage.data, outSize)) { - std::cerr << "unable to write output file" << std::endl; - } -#endif -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -// Test Encode API-0 invalid arguments -TEST(JpegRTest, EncodeAPI0WithInvalidArgs) { - JpegR uHdrLib; - - UhdrCompressedStructWrapper jpgImg(16, 16); - ASSERT_TRUE(jpgImg.allocateMemory()); - - // test quality factor and transfer function - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), -1, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), 101, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - static_cast( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - static_cast(-10), - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - } - - // test dest - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality, - nullptr), - OK) - << "fail, API allows nullptr dest"; - UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr dest"; - } - - // test p010 input - { - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; - - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; - } - - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED)); - ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; - - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg2.setImageColorGamut( - static_cast(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1))); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; - } - - { - const int kWidth = 32, kHeight = 32; - UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - auto rawImgP010 = rawImg.getImageHandle(); - - rawImgP010->width = kWidth - 1; - rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = 0; - rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad luma stride"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth + 64; - rawImgP010->chroma_data = rawImgP010->data; - rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad chroma stride"; - } -} - -/* Test Encode API-1 invalid arguments */ -TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { - JpegR uHdrLib; - - UhdrCompressedStructWrapper jpgImg(16, 16); - ASSERT_TRUE(jpgImg.allocateMemory()); - - // test quality factor and transfer function - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), -1, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), 101, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - static_cast( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - static_cast(-10), - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - } - - // test dest - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality, - nullptr), - OK) - << "fail, API allows nullptr dest"; - UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr dest"; - } - - // test p010 input - { - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; - - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; - } - - { - const int kWidth = 32, kHeight = 32; - UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - auto rawImgP010 = rawImg.getImageHandle(); - UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - auto rawImg420 = rawImg2.getImageHandle(); - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = - static_cast(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; - - rawImgP010->width = kWidth - 1; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = 0; - rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad luma stride"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth + 64; - rawImgP010->chroma_data = rawImgP010->data; - rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad chroma stride"; - } - - // test 420 input - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr 420 image"; - - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr 420 image"; - } - { - const int kWidth = 32, kHeight = 32; - UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - auto rawImgP010 = rawImg.getImageHandle(); - UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - auto rawImg420 = rawImg2.getImageHandle(); - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad 420 color gamut"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->colorGamut = - static_cast(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad 420 color gamut"; - - rawImg420->width = kWidth - 1; - rawImg420->height = kHeight; - rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width for 420"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height for 420"; - - rawImg420->width = 0; - rawImg420->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width for 420"; - - rawImg420->width = kWidth; - rawImg420->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height for 420"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad luma stride for 420"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->luma_stride = 0; - rawImg420->chroma_data = rawImgP010->data; - rawImg420->chroma_stride = kWidth / 2 - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad chroma stride for 420"; - } -} - -/* Test Encode API-2 invalid arguments */ -TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { - JpegR uHdrLib; - - UhdrCompressedStructWrapper jpgImg(16, 16); - ASSERT_TRUE(jpgImg.allocateMemory()); - - // test quality factor and transfer function - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - static_cast( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - static_cast(-10), - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - } - - // test dest - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr), - OK) - << "fail, API allows nullptr dest"; - UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK) - << "fail, API allows nullptr dest"; - } - - // test compressed image - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; - UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; - } - - // test p010 input - { - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; - - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; - } - - { - const int kWidth = 32, kHeight = 32; - UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - auto rawImgP010 = rawImg.getImageHandle(); - UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - auto rawImg420 = rawImg2.getImageHandle(); - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = - static_cast(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; - - rawImgP010->width = kWidth - 1; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = 0; - rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad luma stride"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth + 64; - rawImgP010->chroma_data = rawImgP010->data; - rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad chroma stride"; - } - - // test 420 input - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr 420 image"; - - UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr 420 image"; - } - { - const int kWidth = 32, kHeight = 32; - UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - auto rawImgP010 = rawImg.getImageHandle(); - UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420); - ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg2.allocateMemory()); - auto rawImg420 = rawImg2.getImageHandle(); - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad 420 color gamut"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->colorGamut = - static_cast(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad 420 color gamut"; - - rawImg420->width = kWidth - 1; - rawImg420->height = kHeight; - rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width for 420"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height for 420"; - - rawImg420->width = 0; - rawImg420->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width for 420"; - - rawImg420->width = kWidth; - rawImg420->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height for 420"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad luma stride for 420"; - - rawImg420->width = kWidth; - rawImg420->height = kHeight; - rawImg420->luma_stride = 0; - rawImg420->chroma_data = rawImgP010->data; - rawImg420->chroma_stride = kWidth / 2 - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad chroma stride for 420"; - } -} - -/* Test Encode API-3 invalid arguments */ -TEST(JpegRTest, EncodeAPI3WithInvalidArgs) { - JpegR uHdrLib; - - UhdrCompressedStructWrapper jpgImg(16, 16); - ASSERT_TRUE(jpgImg.allocateMemory()); - - // test quality factor and transfer function - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - static_cast( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - static_cast(-10), - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - } - - // test dest - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr), - OK) - << "fail, API allows nullptr dest"; - UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK) - << "fail, API allows nullptr dest"; - } - - // test compressed image - { - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; - UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; - } - - // test p010 input - { - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; - - UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; - } - - { - const int kWidth = 32, kHeight = 32; - UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImg.allocateMemory()); - auto rawImgP010 = rawImg.getImageHandle(); - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = - static_cast(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; - - rawImgP010->width = kWidth - 1; - rawImgP010->height = kHeight; - rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = 0; - rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; - - rawImgP010->width = kWidth; - rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad luma stride"; - - rawImgP010->width = kWidth; - rawImgP010->height = kHeight; - rawImgP010->luma_stride = kWidth + 64; - rawImgP010->chroma_data = rawImgP010->data; - rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad chroma stride"; - } -} - -/* Test Encode API-4 invalid arguments */ -TEST(JpegRTest, EncodeAPI4WithInvalidArgs) { - UhdrCompressedStructWrapper jpgImg(16, 16); - ASSERT_TRUE(jpgImg.allocateMemory()); - UhdrCompressedStructWrapper jpgImg2(16, 16); - JpegR uHdrLib; - - // test dest - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr), - OK) - << "fail, API allows nullptr dest"; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, - jpgImg2.getImageHandle()), - OK) - << "fail, API allows nullptr dest"; - - // test primary image - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr primary image"; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr primary image"; - - // test gain map - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr gain map image"; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr gain map image"; - - // test metadata - ultrahdr_metadata_struct good_metadata; - good_metadata.version = "1.0"; - good_metadata.minContentBoost = 1.0f; - good_metadata.maxContentBoost = 2.0f; - good_metadata.gamma = 1.0f; - good_metadata.offsetSdr = 0.0f; - good_metadata.offsetHdr = 0.0f; - good_metadata.hdrCapacityMin = 1.0f; - good_metadata.hdrCapacityMax = 2.0f; - - ultrahdr_metadata_struct metadata = good_metadata; - metadata.version = "1.1"; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata version"; - - metadata = good_metadata; - metadata.minContentBoost = 3.0f; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata content boost"; - - metadata = good_metadata; - metadata.gamma = -0.1f; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata gamma"; - - metadata = good_metadata; - metadata.offsetSdr = -0.1f; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata offset sdr"; - - metadata = good_metadata; - metadata.offsetHdr = -0.1f; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata offset hdr"; - - metadata = good_metadata; - metadata.hdrCapacityMax = 0.5f; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata hdr capacity max"; - - metadata = good_metadata; - metadata.hdrCapacityMin = 0.5f; - ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata hdr capacity min"; -} - -/* Test Decode API invalid arguments */ -TEST(JpegRTest, DecodeAPIWithInvalidArgs) { - JpegR uHdrLib; - - UhdrCompressedStructWrapper jpgImg(16, 16); - jpegr_uncompressed_struct destImage{}; - size_t outSize = 16 * 16 * 8; - std::unique_ptr data = std::make_unique(outSize); - destImage.data = data.get(); - - // test jpegr image - ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK) - << "fail, API allows nullptr for jpegr img"; - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK) - << "fail, API allows nullptr for jpegr img"; - ASSERT_TRUE(jpgImg.allocateMemory()); - - // test dest image - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK) - << "fail, API allows nullptr for dest"; - destImage.data = nullptr; - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK) - << "fail, API allows nullptr for dest"; - destImage.data = data.get(); - - // test max display boost - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK) - << "fail, API allows invalid max display boost"; - - // test output format - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr, - static_cast(-1)), - OK) - << "fail, API allows invalid output format"; - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr, - static_cast(ULTRAHDR_OUTPUT_MAX + 1)), - OK) - << "fail, API allows invalid output format"; -} - -TEST(JpegRTest, writeXmpThenRead) { - ultrahdr_metadata_struct metadata_expected; - metadata_expected.version = "1.0"; - metadata_expected.maxContentBoost = 1.25f; - metadata_expected.minContentBoost = 0.75f; - metadata_expected.gamma = 1.0f; - metadata_expected.offsetSdr = 0.0f; - metadata_expected.offsetHdr = 0.0f; - metadata_expected.hdrCapacityMin = 1.0f; - metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost; - const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - - std::string xmp = generateXmpForSecondaryImage(metadata_expected); - - std::vector xmpData; - xmpData.reserve(nameSpaceLength + xmp.size()); - xmpData.insert(xmpData.end(), reinterpret_cast(nameSpace.c_str()), - reinterpret_cast(nameSpace.c_str()) + nameSpaceLength); - xmpData.insert(xmpData.end(), reinterpret_cast(xmp.c_str()), - reinterpret_cast(xmp.c_str()) + xmp.size()); - - ultrahdr_metadata_struct metadata_read; - EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); - EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); - EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); - EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma); - EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr); - EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr); - EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin); - EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax); -} - -class JpegRAPIEncodeAndDecodeTest - : public ::testing::TestWithParam> { -public: - JpegRAPIEncodeAndDecodeTest() - : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){}; - - const ultrahdr_color_gamut mP010ColorGamut; - const ultrahdr_color_gamut mYuv420ColorGamut; -}; - -/* Test Encode API-0 and Decode */ -TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { - // reference encode - UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg.allocateMemory()); - JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK); - // encode with luma stride set - { - UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0)); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28)); - ASSERT_TRUE(rawImg2.setChromaMode(false)); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34)); - ASSERT_TRUE(rawImg2.setChromaMode(false)); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set but no chroma ptr - { - UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38)); - ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - - auto jpg1 = jpgImg.getImageHandle(); -#ifdef DUMP_OUTPUT - if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) { - std::cerr << "unable to write output file" << std::endl; - } -#endif - - ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb")); -} - -/* Test Encode API-1 and Decode */ -TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { - UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImgP010.allocateMemory()); - ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName)); - UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg420.allocateMemory()); - ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg.allocateMemory()); - JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK); - // encode with luma stride set p010 - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set p010 - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256)); - ASSERT_TRUE(rawImg2P010.setChromaMode(false)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with chroma stride set p010 - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64)); - ASSERT_TRUE(rawImg2P010.setChromaMode(false)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set but no chroma ptr p010 - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma stride set 420 - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set 420 - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34)); - ASSERT_TRUE(rawImg2420.setChromaMode(false)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with chroma stride set 420 - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38)); - ASSERT_TRUE(rawImg2420.setChromaMode(false)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set but no chroma ptr 420 - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - - auto jpg1 = jpgImg.getImageHandle(); - -#ifdef DUMP_OUTPUT - if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) { - std::cerr << "unable to write output file" << std::endl; - } -#endif - - ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb")); -} - -/* Test Encode API-2 and Decode */ -TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { - UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImgP010.allocateMemory()); - ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName)); - UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg420.allocateMemory()); - ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg.allocateMemory()); - UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgSdr.allocateMemory()); - auto sdr = jpgSdr.getImageHandle(); - ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length)); - JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK); - // encode with luma stride set - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256)); - ASSERT_TRUE(rawImg2P010.setChromaMode(false)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64)); - ASSERT_TRUE(rawImg2P010.setChromaMode(false)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma stride set - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256)); - ASSERT_TRUE(rawImg2420.setChromaMode(false)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut)); - ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64)); - ASSERT_TRUE(rawImg2420.setChromaMode(false)); - ASSERT_TRUE(rawImg2420.allocateMemory()); - ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - - auto jpg1 = jpgImg.getImageHandle(); - -#ifdef DUMP_OUTPUT - if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) { - std::cerr << "unable to write output file" << std::endl; - } -#endif - - ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb")); -} - -/* Test Encode API-3 and Decode */ -TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { - UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImgP010.allocateMemory()); - ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg.allocateMemory()); - UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgSdr.allocateMemory()); - auto sdr = jpgSdr.getImageHandle(); - ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length)); - JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK); - // encode with luma stride set - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256)); - ASSERT_TRUE(rawImg2P010.setChromaMode(false)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with chroma stride set - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64)); - ASSERT_TRUE(rawImg2P010.setChromaMode(false)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - // encode with luma and chroma stride set and no chroma ptr - { - UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut)); - ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256)); - ASSERT_TRUE(rawImg2P010.allocateMemory()); - ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); - UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); - ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); - auto jpg1 = jpgImg.getImageHandle(); - auto jpg2 = jpgImg2.getImageHandle(); - ASSERT_EQ(jpg1->length, jpg2->length); - ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); - } - - auto jpg1 = jpgImg.getImageHandle(); - -#ifdef DUMP_OUTPUT - if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) { - std::cerr << "unable to write output file" << std::endl; - } -#endif - - ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb")); -} - -INSTANTIATE_TEST_SUITE_P( - JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest, - ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, - ULTRAHDR_COLORGAMUT_BT2100), - ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, - ULTRAHDR_COLORGAMUT_BT2100))); - -// ============================================================================ -// Profiling -// ============================================================================ - -class Profiler { -public: - void timerStart() { gettimeofday(&mStartingTime, nullptr); } - - void timerStop() { gettimeofday(&mEndingTime, nullptr); } - - int64_t elapsedTime() { - struct timeval elapsedMicroseconds; - elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec; - elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec; - return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec; - } - -private: - struct timeval mStartingTime; - struct timeval mEndingTime; -}; - -class JpegRBenchmark : public JpegR { -public: - void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, - ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map); - void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, - ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest); - -private: - const int kProfileCount = 10; -}; - -void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, - jr_uncompressed_ptr p010Image, - ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr map) { - ASSERT_EQ(yuv420Image->width, p010Image->width); - ASSERT_EQ(yuv420Image->height, p010Image->height); - Profiler profileGenerateMap; - profileGenerateMap.timerStart(); - for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, - generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - metadata, map)); - if (i != kProfileCount - 1) delete[] static_cast(map->data); - } - profileGenerateMap.timerStop(); - ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height, - profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f)); -} - -void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, - ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { - Profiler profileRecMap; - profileRecMap.timerStart(); - for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, - applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG, - metadata->maxContentBoost /* displayBoost */, dest)); - } - profileRecMap.timerStop(); - ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height, - profileRecMap.elapsedTime() / (kProfileCount * 1000.f)); -} - -TEST(JpegRTest, ProfileGainMapFuncs) { - UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010); - ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_TRUE(rawImgP010.allocateMemory()); - ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName)); - UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420); - ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); - ASSERT_TRUE(rawImg420.allocateMemory()); - ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName)); - ultrahdr_metadata_struct metadata = {.version = "1.0"}; - jpegr_uncompressed_struct map = {.data = NULL, - .width = 0, - .height = 0, - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - { - auto rawImg = rawImgP010.getImageHandle(); - if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width; - if (!rawImg->chroma_data) { - uint16_t* data = reinterpret_cast(rawImg->data); - rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height; - rawImg->chroma_stride = rawImg->luma_stride; - } - } - { - auto rawImg = rawImg420.getImageHandle(); - if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width; - if (!rawImg->chroma_data) { - uint8_t* data = reinterpret_cast(rawImg->data); - rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height; - rawImg->chroma_stride = rawImg->luma_stride / 2; - } - } - - JpegRBenchmark benchmark; - ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(), - rawImgP010.getImageHandle(), &metadata, - &map)); - - const int dstSize = kImageWidth * kImageWidth * 4; - auto bufferDst = std::make_unique(dstSize); - jpegr_uncompressed_struct dest = {.data = bufferDst.get(), - .width = 0, - .height = 0, - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - - ASSERT_NO_FATAL_FAILURE( - benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest)); -} - -} // namespace android::ultrahdr -- GitLab From e62606d5f3e0fdf916947280d19a167d0e6d9e16 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 22 Feb 2024 11:42:13 -0500 Subject: [PATCH 002/465] SF: Read PRESENT_FENCE_IS_NOT_RELIABLE once Avoid per-frame (hash) lookups and off-main-thread locking. Bug: 324366212 Test: presubmit Change-Id: Ia34bbfe9e6be6f87fac63738a71c62d34a6599db --- services/surfaceflinger/SurfaceFlinger.cpp | 20 +++++++++----------- services/surfaceflinger/SurfaceFlinger.h | 1 + 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index edba50bde6..2ab4b34ed5 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -861,6 +861,9 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { mCompositionEngine->getHwComposer().setCallback(*this); ClientCache::getInstance().setRenderEngine(&getRenderEngine()); + mHasReliablePresentFences = + !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE); + enableLatchUnsignaledConfig = getLatchUnsignaledConfig(); if (base::GetBoolProperty("debug.sf.enable_hwc_vds"s, false)) { @@ -935,9 +938,7 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { // Inform native graphics APIs whether the present timestamp is supported: - const bool presentFenceReliable = - !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE); - mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable); + mStartPropertySetThread = getFactory().createStartPropertySetThread(mHasReliablePresentFences); if (mStartPropertySetThread->Start() != NO_ERROR) { ALOGE("Run StartPropertySetThread failed!"); @@ -1011,9 +1012,7 @@ status_t SurfaceFlinger::getSupportedFrameTimestamps( FrameEvent::RELEASE, }; - ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId); - - if (!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) { + if (mHasReliablePresentFences) { outSupported->push_back(FrameEvent::DISPLAY_PRESENT); } return NO_ERROR; @@ -3037,10 +3036,9 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, // but that should be okay since CompositorTiming has snapping logic. const TimePoint compositeTime = TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp()); - const Duration presentLatency = - getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE) - ? Duration::zero() - : mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime); + const Duration presentLatency = mHasReliablePresentFences + ? mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime) + : Duration::zero(); const auto schedule = mScheduler->getVsyncSchedule(); const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime); @@ -4344,7 +4342,7 @@ void SurfaceFlinger::initScheduler(const sp& display) { features |= Feature::kTracePredictedVsync; } if (!base::GetBoolProperty("debug.sf.vsync_reactor_ignore_present_fences"s, false) && - !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) { + mHasReliablePresentFences) { features |= Feature::kPresentFences; } if (display->refreshRateSelector().kernelIdleTimerController()) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index be057979f9..8fc819d77f 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1221,6 +1221,7 @@ private: // constant members (no synchronization needed for access) const nsecs_t mBootTime = systemTime(); bool mIsUserBuild = true; + bool mHasReliablePresentFences = false; // Can only accessed from the main thread, these members // don't need synchronization -- GitLab From 1ea04a33e42b15ed0d60963e1a385738c5b3f146 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Sat, 10 Feb 2024 03:02:59 +0000 Subject: [PATCH 003/465] InputTracer: Minor readability improvements Bug: 210460522 Test: atest inputflinger_tests Change-Id: I8f97648dc7ce926aec9f84423fc1ef603ca57eae --- .../dispatcher/trace/InputTracer.cpp | 23 +++++++++---------- .../dispatcher/trace/InputTracer.h | 11 +++++---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index be09013a83..0be64e67ca 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -84,24 +84,24 @@ std::unique_ptr InputTracer::traceInboundEvent(const Even void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, const InputTarget& target) { - auto& cookieState = getState(cookie); - if (!cookieState) { + auto& eventState = getState(cookie); + if (eventState.isEventProcessingComplete) { LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()"; } // TODO(b/210460522): Determine if the event is sensitive based on the target. } void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { - auto& cookieState = getState(cookie); - if (!cookieState) { + auto& eventState = getState(cookie); + if (eventState.isEventProcessingComplete) { LOG(FATAL) << "Traced event was already logged. " "eventProcessingComplete() was likely called more than once."; } std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); }, [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }}, - cookieState->event); - cookieState.reset(); + eventState.event); + eventState.isEventProcessingComplete = true; } void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, @@ -136,7 +136,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}}); } -std::optional& InputTracer::getState(const EventTrackerInterface& cookie) { +InputTracer::EventState& InputTracer::getState(const EventTrackerInterface& cookie) { return static_cast(cookie).mState; } @@ -146,18 +146,17 @@ InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent : mTracer(tracer), mState(event) {} InputTracer::EventTrackerImpl::~EventTrackerImpl() { - if (!mState) { + if (mState.isEventProcessingComplete) { // This event has already been written to the trace as expected. return; } - // We're still holding on to the state, which means it hasn't yet been written to the trace. - // Write it to the trace now. + // The event processing was never marked as complete, so do it now. // TODO(b/210460522): Determine why/where the event is being destroyed before // eventProcessingComplete() is called. std::visit(Visitor{[&](const TracedMotionEvent& e) { mTracer.mBackend->traceMotionEvent(e); }, [&](const TracedKeyEvent& e) { mTracer.mBackend->traceKeyEvent(e); }}, - mState->event); - mState.reset(); + mState.event); + mState.isEventProcessingComplete = true; } } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index c8b25c9961..1acac6df20 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -51,13 +51,16 @@ private: // The state of a tracked event. struct EventState { + explicit inline EventState(TracedEvent event) : event(std::move(event)){}; + const TracedEvent event; + bool isEventProcessingComplete{false}; // TODO(b/210460522): Add additional args for tracking event sensitivity and // dispatch target UIDs. }; // Get the event state associated with a tracking cookie. - std::optional& getState(const EventTrackerInterface&); + EventState& getState(const EventTrackerInterface&); // Implementation of the event tracker cookie. The cookie holds the event state directly for // convenience to avoid the overhead of tracking the state separately in InputTracer. @@ -68,11 +71,9 @@ private: private: InputTracer& mTracer; - // This event tracker cookie will only hold the state as long as it has not been written - // to the trace. The state is released when the event is written to the trace. - mutable std::optional mState; + mutable EventState mState; - friend std::optional& InputTracer::getState(const EventTrackerInterface&); + friend EventState& InputTracer::getState(const EventTrackerInterface&); }; }; -- GitLab From 052fb0b3f235249578ca4352d2b08e70bfbd8fe1 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 23 Feb 2024 21:03:12 +0000 Subject: [PATCH 004/465] InputTracer: Ensure eventProcessingComplete called after dropping events There should be no more dispatching decisions made after notifying the tracer that event processing is complete. However, there was a codepath where we were previously dropping events and synthesizing cancelations after calling eventProcessingComplete(). Move the eventProcessingComplete() call to after handling dropped events. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I4b61ef85f6caef345171bfd0a5a01c4367573512 --- .../dispatcher/InputDispatcher.cpp | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8858f0caec..ef7b7805a0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -103,6 +103,26 @@ void ensureEventTraced(const Entry& entry) { } } +// Helper to get a trace tracker from a traced key or motion entry. +const std::unique_ptr& getTraceTracker(const EventEntry& entry) { + switch (entry.type) { + case EventEntry::Type::MOTION: { + const auto& motion = static_cast(entry); + ensureEventTraced(motion); + return motion.traceTracker; + } + case EventEntry::Type::KEY: { + const auto& key = static_cast(entry); + ensureEventTraced(key); + return key.traceTracker; + } + default: { + const static std::unique_ptr kNullTracker; + return kNullTracker; + } + } +} + // Temporarily releases a held mutex for the lifetime of the instance. // Named to match std::scoped_lock class scoped_unlock { @@ -1147,10 +1167,6 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) { dropReason = DropReason::BLOCKED; } done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime); - if (done && mTracer) { - ensureEventTraced(*keyEntry); - mTracer->eventProcessingComplete(*keyEntry->traceTracker); - } break; } @@ -1176,10 +1192,6 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) { } } done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime); - if (done && mTracer) { - ensureEventTraced(*motionEntry); - mTracer->eventProcessingComplete(*motionEntry->traceTracker); - } break; } @@ -1205,6 +1217,12 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) { } mLastDropReason = dropReason; + if (mTracer) { + if (auto& traceTracker = getTraceTracker(*mPendingEvent); traceTracker != nullptr) { + mTracer->eventProcessingComplete(*traceTracker); + } + } + releasePendingEventLocked(); nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately } -- GitLab From 6424edee64235f5a98a6b26d80c9108495a9b025 Mon Sep 17 00:00:00 2001 From: Jyoti Bhayana Date: Tue, 27 Feb 2024 15:33:59 -0800 Subject: [PATCH 005/465] Modify the API for camera privacy allowlist After AAOS IVI review, it was recommended to not categorize the camera privacy allowlisted apps into helpful apps,required apps and helpful and required apps. Therefore removing those categories and changing the name to be more descriptive. Bug: 326182516 Test: Build and test using camera privacy settings UI on automotive devices. Change-Id: Ifa72945b1a7f7985ae4cf4bced0a0bb9f570d178 --- libs/sensorprivacy/Android.bp | 1 - libs/sensorprivacy/SensorPrivacyManager.cpp | 5 ++--- .../hardware/CameraPrivacyAllowlistEntry.aidl | 22 ------------------- .../hardware/ISensorPrivacyManager.aidl | 3 +-- .../sensorprivacy/SensorPrivacyManager.h | 6 ++--- services/sensorservice/SensorService.h | 4 ++-- 6 files changed, 7 insertions(+), 34 deletions(-) delete mode 100644 libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl diff --git a/libs/sensorprivacy/Android.bp b/libs/sensorprivacy/Android.bp index 1e7e70775a..00514c4417 100644 --- a/libs/sensorprivacy/Android.bp +++ b/libs/sensorprivacy/Android.bp @@ -57,7 +57,6 @@ cc_library_shared { filegroup { name: "libsensorprivacy_aidl", srcs: [ - "aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl", "aidl/android/hardware/ISensorPrivacyListener.aidl", "aidl/android/hardware/ISensorPrivacyManager.aidl", ], diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp index fe9378616d..3f3ad9343c 100644 --- a/libs/sensorprivacy/SensorPrivacyManager.cpp +++ b/libs/sensorprivacy/SensorPrivacyManager.cpp @@ -155,10 +155,9 @@ int SensorPrivacyManager::getToggleSensorPrivacyState(int toggleType, int sensor return DISABLED; } -std::vector - SensorPrivacyManager::getCameraPrivacyAllowlist(){ +std::vector SensorPrivacyManager::getCameraPrivacyAllowlist(){ sp service = getService(); - std::vector result; + std::vector result; if (service != nullptr) { service->getCameraPrivacyAllowlist(&result); return result; diff --git a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl b/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl deleted file mode 100644 index 03e153704b..0000000000 --- a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2024, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.hardware; - -parcelable CameraPrivacyAllowlistEntry { - String packageName; - boolean isMandatory; -} diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl index b6bd39e557..f7071872bf 100644 --- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl +++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl @@ -16,7 +16,6 @@ package android.hardware; -import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; /** @hide */ @@ -43,7 +42,7 @@ interface ISensorPrivacyManager { void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable); - List getCameraPrivacyAllowlist(); + List getCameraPrivacyAllowlist(); int getToggleSensorPrivacyState(int toggleType, int sensor); diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h index 9e97e166be..8935b76adc 100644 --- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h +++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h @@ -45,9 +45,7 @@ public: enum { ENABLED = 1, DISABLED = 2, - AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = 3, - AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = 4, - AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = 5 + ENABLED_EXCEPT_ALLOWLISTED_APPS = 3 }; SensorPrivacyManager(); @@ -62,7 +60,7 @@ public: bool isToggleSensorPrivacyEnabled(int toggleType, int sensor); status_t isToggleSensorPrivacyEnabled(int toggleType, int sensor, bool &result); int getToggleSensorPrivacyState(int toggleType, int sensor); - std::vector getCameraPrivacyAllowlist(); + std::vector getCameraPrivacyAllowlist(); bool isCameraPrivacyEnabled(String16 packageName); status_t linkToDeath(const sp& recipient); diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 118d9281fc..bd54d24a9f 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -340,8 +340,8 @@ private: binder::Status onSensorPrivacyChanged(int toggleType, int sensor, bool enabled); - // This callback is used for additional automotive-specific states for sensor privacy - // such as AUTO_DRIVER_ASSISTANCE_APPS. The newly defined states will only be valid + // This callback is used for additional automotive-specific state for sensor privacy + // such as ENABLED_EXCEPT_ALLOWLISTED_APPS. The newly defined states will only be valid // for camera privacy on automotive devices. onSensorPrivacyChanged() will still be // invoked whenever the enabled status of a toggle changes. binder::Status onSensorPrivacyStateChanged(int, int, int) {return binder::Status::ok();} -- GitLab From 475c4131cf027ed64a53587db0bca6f36b35ecb2 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 29 Feb 2024 15:56:15 +0000 Subject: [PATCH 006/465] Add types for native Choreographer to ensure ordering For now, added type CALLBACK_INPUT and CALLBACK_ANIMATION. The default callback type will be CALLBACK_ANIMATION for the public APIs. CALLBACK_INPUT will always run before CALLBACK_ANIMATION. Test: ChoreographerTest Bug: 324271765 Change-Id: I25c92b788bb83778d5b0608a47efe9756d42e32f --- libs/gui/Choreographer.cpp | 46 +++++++++----- libs/gui/include/gui/Choreographer.h | 10 ++- libs/gui/tests/Android.bp | 2 + libs/gui/tests/Choreographer_test.cpp | 88 +++++++++++++++++++++++++++ libs/nativedisplay/AChoreographer.cpp | 12 ++-- 5 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 libs/gui/tests/Choreographer_test.cpp diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp index 4518b67d4c..54290cd629 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -143,9 +143,9 @@ Choreographer::~Choreographer() { void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, AChoreographer_vsyncCallback vsyncCallback, void* data, - nsecs_t delay) { + nsecs_t delay, CallbackType callbackType) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay}; + FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay, callbackType}; { std::lock_guard _l{mLock}; mFrameCallbacks.push(callback); @@ -285,18 +285,8 @@ void Choreographer::handleRefreshRateUpdates() { } } -void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t, - VsyncEventData vsyncEventData) { - std::vector callbacks{}; - { - std::lock_guard _l{mLock}; - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) { - callbacks.push_back(mFrameCallbacks.top()); - mFrameCallbacks.pop(); - } - } - mLastVsyncEventData = vsyncEventData; +void Choreographer::dispatchCallbacks(const std::vector& callbacks, + VsyncEventData vsyncEventData, nsecs_t timestamp) { for (const auto& cb : callbacks) { if (cb.vsyncCallback != nullptr) { ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64, @@ -319,6 +309,34 @@ void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t } } +void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t, + VsyncEventData vsyncEventData) { + std::vector animationCallbacks{}; + std::vector inputCallbacks{}; + { + std::lock_guard _l{mLock}; + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) { + if (mFrameCallbacks.top().callbackType == CALLBACK_INPUT) { + inputCallbacks.push_back(mFrameCallbacks.top()); + } else { + animationCallbacks.push_back(mFrameCallbacks.top()); + } + mFrameCallbacks.pop(); + } + } + mLastVsyncEventData = vsyncEventData; + // Callbacks with type CALLBACK_INPUT should always run first + { + ATRACE_FORMAT("CALLBACK_INPUT"); + dispatchCallbacks(inputCallbacks, vsyncEventData, timestamp); + } + { + ATRACE_FORMAT("CALLBACK_ANIMATION"); + dispatchCallbacks(animationCallbacks, vsyncEventData, timestamp); + } +} + void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) { ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this, to_string(displayId).c_str(), toString(connected)); diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h index 55a7aa7ddc..fc79b03c23 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -28,12 +28,18 @@ namespace android { using gui::VsyncEventData; +enum CallbackType : int8_t { + CALLBACK_INPUT, + CALLBACK_ANIMATION, +}; + struct FrameCallback { AChoreographer_frameCallback callback; AChoreographer_frameCallback64 callback64; AChoreographer_vsyncCallback vsyncCallback; void* data; nsecs_t dueTime; + CallbackType callbackType; inline bool operator<(const FrameCallback& rhs) const { // Note that this is intentionally flipped because we want callbacks due sooner to be at @@ -78,7 +84,7 @@ public: void postFrameCallbackDelayed(AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, AChoreographer_vsyncCallback vsyncCallback, void* data, - nsecs_t delay); + nsecs_t delay, CallbackType callbackType); void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data) EXCLUDES(gChoreographers.lock); void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data); @@ -109,6 +115,8 @@ private: void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, VsyncEventData vsyncEventData) override; + void dispatchCallbacks(const std::vector&, VsyncEventData vsyncEventData, + nsecs_t timestamp); void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override; void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp index e606b9941e..0f16f714dc 100644 --- a/libs/gui/tests/Android.bp +++ b/libs/gui/tests/Android.bp @@ -30,6 +30,7 @@ cc_test { "BLASTBufferQueue_test.cpp", "BufferItemConsumer_test.cpp", "BufferQueue_test.cpp", + "Choreographer_test.cpp", "CompositorTiming_test.cpp", "CpuConsumer_test.cpp", "EndToEndNativeInputTest.cpp", @@ -61,6 +62,7 @@ cc_test { "libSurfaceFlingerProp", "libGLESv1_CM", "libinput", + "libnativedisplay", ], static_libs: [ diff --git a/libs/gui/tests/Choreographer_test.cpp b/libs/gui/tests/Choreographer_test.cpp new file mode 100644 index 0000000000..2ac2550f07 --- /dev/null +++ b/libs/gui/tests/Choreographer_test.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 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. + */ + +#define LOG_TAG "Choreographer_test" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +class ChoreographerTest : public ::testing::Test {}; + +struct VsyncCallback { + std::atomic completePromise{false}; + std::chrono::nanoseconds frameTime{0LL}; + std::chrono::nanoseconds receivedCallbackTime{0LL}; + + void onVsyncCallback(const AChoreographerFrameCallbackData* callbackData) { + frameTime = std::chrono::nanoseconds{ + AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData)}; + receivedCallbackTime = std::chrono::nanoseconds{systemTime(SYSTEM_TIME_MONOTONIC)}; + completePromise.store(true); + } + + bool callbackReceived() { return completePromise.load(); } +}; + +static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) { + VsyncCallback* cb = static_cast(data); + cb->onVsyncCallback(callbackData); +} + +TEST_F(ChoreographerTest, InputCallbackBeforeAnimation) { + sp looper = Looper::prepare(0); + Choreographer* choreographer = Choreographer::getForThread(); + VsyncCallback animationCb; + VsyncCallback inputCb; + + choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &animationCb, 0, + CALLBACK_ANIMATION); + choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &inputCb, 0, + CALLBACK_INPUT); + + nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); + nsecs_t currTime; + int pollResult; + do { + pollResult = looper->pollOnce(16); + currTime = systemTime(SYSTEM_TIME_MONOTONIC); + } while (!(inputCb.callbackReceived() && animationCb.callbackReceived()) && + (pollResult != Looper::POLL_TIMEOUT && pollResult != Looper::POLL_ERROR) && + (currTime - startTime < 3000)); + + ASSERT_TRUE(inputCb.callbackReceived()) << "did not receive input callback"; + ASSERT_TRUE(animationCb.callbackReceived()) << "did not receive animation callback"; + + ASSERT_EQ(inputCb.frameTime, animationCb.frameTime) + << android::base::StringPrintf("input and animation callback frame times don't match. " + "inputFrameTime=%lld animationFrameTime=%lld", + inputCb.frameTime.count(), + animationCb.frameTime.count()); + + ASSERT_LT(inputCb.receivedCallbackTime, animationCb.receivedCallbackTime) + << android::base::StringPrintf("input callback was not called first. " + "inputCallbackTime=%lld animationCallbackTime=%lld", + inputCb.frameTime.count(), + animationCb.frameTime.count()); +} + +} // namespace android \ No newline at end of file diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp index 8f005a56f8..bed31e27a8 100644 --- a/libs/nativedisplay/AChoreographer.cpp +++ b/libs/nativedisplay/AChoreographer.cpp @@ -148,29 +148,31 @@ AChoreographer* AChoreographer_getInstance() { void AChoreographer_postFrameCallback(AChoreographer* choreographer, AChoreographer_frameCallback callback, void* data) { AChoreographer_to_Choreographer(choreographer) - ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0); + ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0, CALLBACK_ANIMATION); } void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer, AChoreographer_frameCallback callback, void* data, long delayMillis) { AChoreographer_to_Choreographer(choreographer) - ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis)); + ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis), + CALLBACK_ANIMATION); } void AChoreographer_postVsyncCallback(AChoreographer* choreographer, AChoreographer_vsyncCallback callback, void* data) { AChoreographer_to_Choreographer(choreographer) - ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0); + ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0, CALLBACK_ANIMATION); } void AChoreographer_postFrameCallback64(AChoreographer* choreographer, AChoreographer_frameCallback64 callback, void* data) { AChoreographer_to_Choreographer(choreographer) - ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0); + ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0, CALLBACK_ANIMATION); } void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer, AChoreographer_frameCallback64 callback, void* data, uint32_t delayMillis) { AChoreographer_to_Choreographer(choreographer) - ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis)); + ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis), + CALLBACK_ANIMATION); } void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer, AChoreographer_refreshRateCallback callback, -- GitLab From 50582ba78ac8843a6163a4d256691c500932f4d9 Mon Sep 17 00:00:00 2001 From: Cody Heiner Date: Tue, 20 Feb 2024 17:47:37 -0800 Subject: [PATCH 007/465] Fix possible crash in scale-invariant error calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The logic for the scale-invariant calculation was quite complex in the existing code, and depended on some tricky chains of logical dependence between scale-invariant errors and general errors. This change firstly eliminates unnecessary nesting by pulling the scale-invariant error calculation out of the loop – the full calculation only occurs once per computeAtomFields call, within its own loop. Secondly, instead of crashing under certain conditions when the scale-invariant error count is zero, this changes the code to simply not compute the error in this case. (The complex chain of logic I had followed to initially add the fatal crash turned out to be fallacious.) Finally, this adds a testcase that fails without the changes to the MetricsManager implementation (see ag/26418604). This added testcase represents skipped/dropped input events, or an input interval greater than the prediction interval. Test: atest frameworks/native/libs/input/tests/MotionPredictorMetricsManager_test.cpp Test: the above test fails without the changes to MotionPredictionMetricsManager.cpp Test: `statsd_testdrive 718`, then draw with stylus → reported metrics are reasonable Bug: 325711945 Change-Id: Ic56c0f0c810ec1b85b1906e16a8640824187d1fb --- libs/input/MotionPredictorMetricsManager.cpp | 69 +++++++++------- .../MotionPredictorMetricsManager_test.cpp | 81 ++++++++++++------- 2 files changed, 92 insertions(+), 58 deletions(-) diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp index 0412d08181..6872af2aa5 100644 --- a/libs/input/MotionPredictorMetricsManager.cpp +++ b/libs/input/MotionPredictorMetricsManager.cpp @@ -113,7 +113,12 @@ void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) { // Adds new predictions to mRecentPredictions and maintains the invariant that elements are // sorted in ascending order of targetTimestamp. void MotionPredictorMetricsManager::onPredict(const MotionEvent& predictionEvent) { - for (size_t i = 0; i < predictionEvent.getHistorySize() + 1; ++i) { + const size_t numPredictions = predictionEvent.getHistorySize() + 1; + if (numPredictions > mMaxNumPredictions) { + LOG(WARNING) << "numPredictions (" << numPredictions << ") > mMaxNumPredictions (" + << mMaxNumPredictions << "). Ignoring extra predictions in metrics."; + } + for (size_t i = 0; (i < numPredictions) && (i < mMaxNumPredictions); ++i) { // Convert MotionEvent to PredictionPoint. const PointerCoords* coords = predictionEvent.getHistoricalRawPointerCoords(/*pointerIndex=*/0, i); @@ -325,42 +330,44 @@ void MotionPredictorMetricsManager::computeAtomFields() { mAtomFields[i].highVelocityOffTrajectoryRmse = static_cast(offTrajectoryRmse * 1000); } + } - // Scale-invariant errors: reported only for the last time bucket, where the values - // represent an average across all time buckets. - if (i + 1 == mMaxNumPredictions) { - // Compute error averages. - float alongTrajectoryRmseSum = 0; - float offTrajectoryRmseSum = 0; - for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) { - // If we have general errors (checked above), we should always also have - // scale-invariant errors. - LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantErrorsCount == 0, - "mAggregatedMetrics[%zu].scaleInvariantErrorsCount is 0", j); - - LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0, - "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f " - "should not be negative", - j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse); - alongTrajectoryRmseSum += - std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse / - mAggregatedMetrics[j].scaleInvariantErrorsCount); - - LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0, - "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f " - "should not be negative", - j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse); - offTrajectoryRmseSum += - std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse / - mAggregatedMetrics[j].scaleInvariantErrorsCount); + // Scale-invariant errors: the average scale-invariant error across all time buckets + // is reported in the last time bucket. + { + // Compute error averages. + float alongTrajectoryRmseSum = 0; + float offTrajectoryRmseSum = 0; + int bucket_count = 0; + for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) { + if (mAggregatedMetrics[j].scaleInvariantErrorsCount == 0) { + continue; } - const float averageAlongTrajectoryRmse = - alongTrajectoryRmseSum / mAggregatedMetrics.size(); + LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0, + "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f " + "should not be negative", + j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse); + alongTrajectoryRmseSum += + std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse / + mAggregatedMetrics[j].scaleInvariantErrorsCount); + + LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0, + "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f " + "should not be negative", + j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse); + offTrajectoryRmseSum += std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse / + mAggregatedMetrics[j].scaleInvariantErrorsCount); + + ++bucket_count; + } + + if (bucket_count > 0) { + const float averageAlongTrajectoryRmse = alongTrajectoryRmseSum / bucket_count; mAtomFields.back().scaleInvariantAlongTrajectoryRmse = static_cast(averageAlongTrajectoryRmse * 1000); - const float averageOffTrajectoryRmse = offTrajectoryRmseSum / mAggregatedMetrics.size(); + const float averageOffTrajectoryRmse = offTrajectoryRmseSum / bucket_count; mAtomFields.back().scaleInvariantOffTrajectoryRmse = static_cast(averageOffTrajectoryRmse * 1000); } diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp index 31cc1459fc..cc41eeb5e7 100644 --- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp +++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp @@ -238,14 +238,17 @@ TEST(MakeMotionEventTest, MakeLiftMotionEvent) { // --- Ground-truth-generation helper functions. --- +// Generates numPoints ground truth points with values equal to those of the given +// GroundTruthPoint, and with consecutive timestamps separated by the given inputInterval. std::vector generateConstantGroundTruthPoints( - const GroundTruthPoint& groundTruthPoint, size_t numPoints) { + const GroundTruthPoint& groundTruthPoint, size_t numPoints, + nsecs_t inputInterval = TEST_PREDICTION_INTERVAL_NANOS) { std::vector groundTruthPoints; nsecs_t timestamp = groundTruthPoint.timestamp; for (size_t i = 0; i < numPoints; ++i) { groundTruthPoints.emplace_back(groundTruthPoint); groundTruthPoints.back().timestamp = timestamp; - timestamp += TEST_PREDICTION_INTERVAL_NANOS; + timestamp += inputInterval; } return groundTruthPoints; } @@ -280,7 +283,8 @@ TEST(GenerateConstantGroundTruthPointsTest, BasicTest) { const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f}, .timestamp = TEST_INITIAL_TIMESTAMP}; const std::vector groundTruthPoints = - generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3); + generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3, + /*inputInterval=*/10); ASSERT_EQ(3u, groundTruthPoints.size()); // First point. @@ -290,11 +294,11 @@ TEST(GenerateConstantGroundTruthPointsTest, BasicTest) { // Second point. EXPECT_EQ(groundTruthPoints[1].position, groundTruthPoint.position); EXPECT_EQ(groundTruthPoints[1].pressure, groundTruthPoint.pressure); - EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp); + EXPECT_EQ(groundTruthPoints[1].timestamp, groundTruthPoint.timestamp + 10); // Third point. EXPECT_EQ(groundTruthPoints[2].position, groundTruthPoint.position); EXPECT_EQ(groundTruthPoints[2].pressure, groundTruthPoint.pressure); - EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp); + EXPECT_EQ(groundTruthPoints[2].timestamp, groundTruthPoint.timestamp + 20); } TEST(GenerateCircularArcGroundTruthTest, StraightLineUpwards) { @@ -333,16 +337,19 @@ TEST(GenerateCircularArcGroundTruthTest, CounterclockwiseSquare) { // --- Prediction-generation helper functions. --- -// Creates a sequence of predictions with values equal to those of the given GroundTruthPoint. -std::vector generateConstantPredictions(const GroundTruthPoint& groundTruthPoint) { +// Generates TEST_MAX_NUM_PREDICTIONS predictions with values equal to those of the given +// GroundTruthPoint, and with consecutive timestamps separated by the given predictionInterval. +std::vector generateConstantPredictions( + const GroundTruthPoint& groundTruthPoint, + nsecs_t predictionInterval = TEST_PREDICTION_INTERVAL_NANOS) { std::vector predictions; - nsecs_t predictionTimestamp = groundTruthPoint.timestamp + TEST_PREDICTION_INTERVAL_NANOS; + nsecs_t predictionTimestamp = groundTruthPoint.timestamp + predictionInterval; for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) { predictions.push_back(PredictionPoint{{.position = groundTruthPoint.position, .pressure = groundTruthPoint.pressure}, .originTimestamp = groundTruthPoint.timestamp, .targetTimestamp = predictionTimestamp}); - predictionTimestamp += TEST_PREDICTION_INTERVAL_NANOS; + predictionTimestamp += predictionInterval; } return predictions; } @@ -375,8 +382,9 @@ std::vector generatePredictionsByLinearExtrapolation( TEST(GeneratePredictionsTest, GenerateConstantPredictions) { const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f}, .timestamp = TEST_INITIAL_TIMESTAMP}; + const nsecs_t predictionInterval = 10; const std::vector predictionPoints = - generateConstantPredictions(groundTruthPoint); + generateConstantPredictions(groundTruthPoint, predictionInterval); ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size()); for (size_t i = 0; i < predictionPoints.size(); ++i) { @@ -385,8 +393,7 @@ TEST(GeneratePredictionsTest, GenerateConstantPredictions) { EXPECT_THAT(predictionPoints[i].pressure, FloatNear(groundTruthPoint.pressure, 1e-6)); EXPECT_EQ(predictionPoints[i].originTimestamp, groundTruthPoint.timestamp); EXPECT_EQ(predictionPoints[i].targetTimestamp, - groundTruthPoint.timestamp + - static_cast(i + 1) * TEST_PREDICTION_INTERVAL_NANOS); + TEST_INITIAL_TIMESTAMP + static_cast(i + 1) * predictionInterval); } } @@ -678,12 +685,9 @@ ReportAtomFunction createMockReportAtomFunction(std::vector& reporte // • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements. // • predictionPoints: the first index points to a vector of predictions corresponding to the // source ground truth point with the same index. -// - The first element should be empty, because there are not expected to be predictions until -// we have received 2 ground truth points. -// - The last element may be empty, because there will be no future ground truth points to -// associate with those predictions (if not empty, it will be ignored). +// - For empty prediction vectors, MetricsManager::onPredict will not be called. // - To test all prediction buckets, there should be at least TEST_MAX_NUM_PREDICTIONS non-empty -// prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and +// prediction vectors (that is, excluding the first and last). Thus, groundTruthPoints and // predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2. // // When the function returns, outReportedAtomFields will contain the reported AtomFields. @@ -697,19 +701,12 @@ void runMetricsManager(const std::vector& groundTruthPoints, createMockReportAtomFunction( outReportedAtomFields)); - // Validate structure of groundTruthPoints and predictionPoints. - ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size()); ASSERT_GE(groundTruthPoints.size(), 2u); - ASSERT_EQ(predictionPoints[0].size(), 0u); - for (size_t i = 1; i + 1 < predictionPoints.size(); ++i) { - SCOPED_TRACE(testing::Message() << "i = " << i); - ASSERT_EQ(predictionPoints[i].size(), TEST_MAX_NUM_PREDICTIONS); - } + ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size()); - // Pass ground truth points and predictions (for all except first and last ground truth). for (size_t i = 0; i < groundTruthPoints.size(); ++i) { metricsManager.onRecord(makeMotionEvent(groundTruthPoints[i])); - if ((i > 0) && (i + 1 < predictionPoints.size())) { + if (!predictionPoints[i].empty()) { metricsManager.onPredict(makeMotionEvent(predictionPoints[i])); } } @@ -738,7 +735,7 @@ TEST(MotionPredictorMetricsManagerTest, NoPredictions) { // Perfect predictions test: // • Input: constant input events, perfect predictions matching the input events. // • Expectation: all error metrics should be zero, or NO_DATA_SENTINEL for "unreported" metrics. -// (For example, scale-invariant errors are only reported for the final time bucket.) +// (For example, scale-invariant errors are only reported for the last time bucket.) TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) { GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f}, .timestamp = TEST_INITIAL_TIMESTAMP}; @@ -977,5 +974,35 @@ TEST(MotionPredictorMetricsManagerTest, CounterclockwiseOctagonGroundTruthLinear } } +// Robustness test: +// • Input: input events separated by a significantly greater time interval than the interval +// between predictions. +// • Expectation: the MetricsManager should not crash in this case. (No assertions are made about +// the resulting metrics.) +// +// In practice, this scenario could arise either if the input and prediction intervals are +// mismatched, or if input events are missing (dropped or skipped for some reason). +TEST(MotionPredictorMetricsManagerTest, MismatchedInputAndPredictionInterval) { + // Create two ground truth points separated by MAX_NUM_PREDICTIONS * PREDICTION_INTERVAL, + // so that the second ground truth point corresponds to the last prediction bucket. This + // ensures that the scale-invariant error codepath will be run, giving full code coverage. + GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(0.0f, 0.0f), .pressure = 0.5f}, + .timestamp = TEST_INITIAL_TIMESTAMP}; + const nsecs_t inputInterval = TEST_MAX_NUM_PREDICTIONS * TEST_PREDICTION_INTERVAL_NANOS; + const std::vector groundTruthPoints = + generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/2, inputInterval); + + // Create predictions separated by the prediction interval. + std::vector> predictionPoints; + for (size_t i = 0; i < groundTruthPoints.size(); ++i) { + predictionPoints.push_back( + generateConstantPredictions(groundTruthPoints[i], TEST_PREDICTION_INTERVAL_NANOS)); + } + + // Test that we can run the MetricsManager without crashing. + std::vector reportedAtomFields; + runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields); +} + } // namespace } // namespace android -- GitLab From 9633f8e8902ab50891e8a825b850fbea74a9a21d Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 4 Mar 2024 19:38:12 +0000 Subject: [PATCH 008/465] Revert "SF: Introduce VsyncTimeline to VsyncPredictor" This reverts commit b6c7f880460c81a6ce49ccb3334e2d2e1e020f81. Reason for revert: Regressions tracked as childs on b/326599221 Change-Id: Ic0f959113a2d434d3b6412c90b58b85e5151e436 --- .../Scheduler/VSyncDispatchTimerQueue.cpp | 71 ++--- .../Scheduler/VSyncDispatchTimerQueue.h | 4 +- .../Scheduler/VSyncPredictor.cpp | 298 +++++------------- .../surfaceflinger/Scheduler/VSyncPredictor.h | 60 +--- .../surfaceflinger/Scheduler/VSyncTracker.h | 6 +- .../Scheduler/VsyncSchedule.cpp | 4 +- .../surfaceflinger/Scheduler/VsyncSchedule.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 12 +- .../tests/unittests/SchedulerTest.cpp | 11 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 6 +- .../tests/unittests/VSyncPredictorTest.cpp | 101 ++---- .../tests/unittests/mock/MockVSyncTracker.h | 4 +- 12 files changed, 171 insertions(+), 408 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 6d6b70d198..84ccf8e938 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -44,17 +44,6 @@ ScheduleResult getExpectedCallbackTime(nsecs_t nextVsyncTime, TimePoint::fromNs(nextVsyncTime)}; } -void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) { - if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) { - return; - } - - ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ", - ns2us(*entry.wakeupTime() - now), "us; VSYNC in ", - ns2us(*entry.targetVsync() - now), "us"); - ATRACE_FORMAT_INSTANT(trace.c_str()); -} - } // namespace VSyncDispatch::~VSyncDispatch() = default; @@ -98,7 +87,6 @@ std::optional VSyncDispatchTimerQueueEntry::targetVsync() const { ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker, nsecs_t now) { - ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule"); auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync, now + timing.workDuration + @@ -110,8 +98,6 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance)); bool const wouldSkipAWakeup = mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance))); - ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), - wouldSkipAVsyncTarget, wouldSkipAWakeup); if (FlagManager::getInstance().dont_skip_on_early_ro()) { if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { nextVsyncTime = mArmedInfo->mActualVsyncTime; @@ -136,7 +122,7 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate( VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) { mWorkloadUpdateInfo = timing; - const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo); + const auto armedInfo = update(tracker, now, timing, mArmedInfo); return {TimePoint::fromNs(armedInfo.mActualWakeupTime), TimePoint::fromNs(armedInfo.mActualVsyncTime)}; } @@ -154,13 +140,11 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, bool const nextVsyncTooClose = mLastDispatchTime && (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod; if (alreadyDispatchedForVsync) { - ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance, *mLastDispatchTime); } if (nextVsyncTooClose) { - ATRACE_FORMAT_INSTANT("nextVsyncTooClose"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod, *mLastDispatchTime + currentPeriod); } @@ -168,11 +152,9 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, return nextVsyncTime; } -auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now, - VSyncDispatch::ScheduleTiming timing, - std::optional armedInfo) const - -> ArmingInfo { - ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo"); +auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, + VSyncDispatch::ScheduleTiming timing, + std::optional armedInfo) const -> ArmingInfo { const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration; const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync); @@ -183,39 +165,29 @@ auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t n const auto nextReadyTime = nextVsyncTime - timing.readyDuration; const auto nextWakeupTime = nextReadyTime - timing.workDuration; - if (FlagManager::getInstance().dont_skip_on_early_ro()) { - bool const wouldSkipAVsyncTarget = - armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); - bool const wouldSkipAWakeup = - armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); - ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), - wouldSkipAVsyncTarget, wouldSkipAWakeup); - if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { - return *armedInfo; - } + bool const wouldSkipAVsyncTarget = + armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); + bool const wouldSkipAWakeup = + armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); + if (FlagManager::getInstance().dont_skip_on_early_ro() && + (wouldSkipAVsyncTarget || wouldSkipAWakeup)) { + return *armedInfo; } return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime}; } void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { - ATRACE_NAME("VSyncDispatchTimerQueueEntry::update"); if (!mArmedInfo && !mWorkloadUpdateInfo) { return; } if (mWorkloadUpdateInfo) { - const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration; - const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration; - const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync; - ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64 - " lastVsyncDelta=%" PRId64, - workDelta, readyDelta, lastVsyncDelta); mScheduleTiming = *mWorkloadUpdateInfo; mWorkloadUpdateInfo.reset(); } - mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo); + mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo); } void VSyncDispatchTimerQueueEntry::disarm() { @@ -310,7 +282,6 @@ void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) { void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( nsecs_t now, CallbackMap::const_iterator skipUpdateIt) { - ATRACE_CALL(); std::optional min; std::optional targetVsync; std::optional nextWakeupName; @@ -323,10 +294,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( if (it != skipUpdateIt) { callback->update(*mTracker, now); } - - traceEntry(*callback, now); - - const auto wakeupTime = *callback->wakeupTime(); + auto const wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { nextWakeupName = callback->name(); min = wakeupTime; @@ -335,6 +303,11 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (min && min < mIntendedWakeupTime) { + if (ATRACE_ENABLED() && nextWakeupName && targetVsync) { + ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now), + "us; VSYNC in ", ns2us(*targetVsync - now), "us"); + ATRACE_NAME(trace.c_str()); + } setTimer(*min, now); } else { ATRACE_NAME("cancel timer"); @@ -343,7 +316,6 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } void VSyncDispatchTimerQueue::timerCallback() { - ATRACE_CALL(); struct Invocation { std::shared_ptr callback; nsecs_t vsyncTimestamp; @@ -366,9 +338,8 @@ void VSyncDispatchTimerQueue::timerCallback() { continue; } - traceEntry(*callback, now); - auto const readyTime = callback->readyTime(); + auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast(0)); if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) { callback->executing(); @@ -382,8 +353,6 @@ void VSyncDispatchTimerQueue::timerCallback() { } for (auto const& invocation : invocations) { - ftl::Concat trace(ftl::truncated<5>(invocation.callback->name())); - ATRACE_FORMAT("%s: %s", __func__, trace.c_str()); invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp, invocation.deadlineTimestamp); } diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index e4ddc03480..252c09ce53 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -91,8 +91,8 @@ private: }; nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const; - ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, - std::optional) const; + ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, + std::optional) const; const std::string mName; const VSyncDispatch::Callback mCallback; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 2f9dfeaff7..8697696915 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -45,35 +45,11 @@ using base::StringAppendF; static auto constexpr kMaxPercent = 100u; -namespace { -nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime, - std::optional lastVsyncOpt) { - const auto threshold = model.slope / 2; - - if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { - const auto vsyncDiff = vsyncTime - *lastVsyncOpt; - if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) { - const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; - ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. " - "adjust by %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, - static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, - static_cast(vsyncFixup) / 1e6f); - return vsyncFixup; - } - } - - return 0; -} -} // namespace - VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNull modePtr, - size_t historySize, size_t minimumSamplesForPrediction, - uint32_t outlierTolerancePercent) - : mClock(std::move(clock)), - mId(modePtr->getPhysicalDisplayId()), +VSyncPredictor::VSyncPredictor(ftl::NonNull modePtr, size_t historySize, + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) + : mId(modePtr->getPhysicalDisplayId()), mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), @@ -171,7 +147,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { mKnownTimestamp = timestamp; } ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago", - (mClock->now() - *mKnownTimestamp) / 1e6f); + (systemTime() - *mKnownTimestamp) / 1e6f); return false; } @@ -274,6 +250,17 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { return true; } +auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { + const auto vsync = snapToVsync(timestamp); + if (!mLastVsyncSequence) return {vsync, 0}; + + const auto [slope, _] = getVSyncPredictionModelLocked(); + const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; + const auto vsyncSequence = lastVsyncSequence + + static_cast(std::round((vsync - lastVsyncTime) / static_cast(slope))); + return {vsync, vsyncSequence}; +} + nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); @@ -311,32 +298,51 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { } nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt) { + std::optional lastVsyncOpt) const { ATRACE_CALL(); std::lock_guard lock(mMutex); + const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; + const auto threshold = currentPeriod / 2; + const auto minFramePeriod = minFramePeriodLocked().ns(); + const auto lastFrameMissed = + lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold; + const nsecs_t baseTime = + FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt + ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold) + : timePoint; + return snapToVsyncAlignedWithRenderRate(baseTime); +} - const auto now = TimePoint::fromNs(mClock->now()); - purgeTimelines(now); +nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const { + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - std::optional vsyncOpt; - for (auto& timeline : mTimelines) { - vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(), - minFramePeriodLocked(), - snapToVsync(timePoint), mMissedVsync, - lastVsyncOpt); - if (vsyncOpt) { - break; + const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { + if (!mRenderRateOpt) return 0; + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), + *mRenderRateOpt); + if (divisor <= 1) return 0; + + int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + // This is actually a bug fix, but guarded with vrr_config since we found it with this + // config + if (FlagManager::getInstance().vrr_config()) { + if (mod < 0) mod += divisor; } - } - LOG_ALWAYS_FATAL_IF(!vsyncOpt); - if (*vsyncOpt > mLastCommittedVsync) { - mLastCommittedVsync = *vsyncOpt; - ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms", - float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f); + return divisor - mod; + }(); + + if (renderRatePhase == 0) { + return mLastVsyncSequence->vsyncTime; } - return vsyncOpt->ns(); + auto const [slope, intercept] = getVSyncPredictionModelLocked(); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; + return snapToVsync(approximateNextVsync - slope / 2); } /* @@ -347,28 +353,32 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, * isVSyncInPhase(33.3, 30) = false * isVSyncInPhase(50.0, 30) = true */ -bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) { - if (timePoint == 0) { - return true; - } - +bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { std::lock_guard lock(mMutex); - const auto model = getVSyncPredictionModelLocked(); - const nsecs_t period = model.slope; - const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const auto now = TimePoint::fromNs(mClock->now()); - const auto vsync = snapToVsync(justBeforeTimePoint); + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), + frameRate); + return isVSyncInPhaseLocked(timePoint, static_cast(divisor)); +} - purgeTimelines(now); +bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { + const TimePoint now = TimePoint::now(); + const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float { + return ticks(TimePoint::fromNs(timePoint) - now); + }; + ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint), + divisor); - for (auto& timeline : mTimelines) { - if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) { - return timeline.isVSyncInPhase(model, vsync, frameRate); - } + if (divisor <= 1 || timePoint == 0) { + return true; } - // The last timeline should always be valid - return mTimelines.back().isVSyncInPhase(model, vsync, frameRate); + const nsecs_t period = mRateMap[idealPeriod()].slope; + const nsecs_t justBeforeTimePoint = timePoint - period / 2; + const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, + getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); + return vsyncSequence.seq % divisor == 0; } void VSyncPredictor::setRenderRate(Fps renderRate) { @@ -376,9 +386,6 @@ void VSyncPredictor::setRenderRate(Fps renderRate) { ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str()); std::lock_guard lock(mMutex); mRenderRateOpt = renderRate; - mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); - mTimelines.emplace_back(mIdealPeriod, renderRate); - purgeTimelines(TimePoint::fromNs(mClock->now())); } void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { @@ -408,9 +415,8 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { clearTimestamps(); } -Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, - TimePoint lastConfirmedPresentTime) { - ATRACE_CALL(); +void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, + TimePoint lastConfirmedPresentTime) { const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; const auto threshold = currentPeriod / 2; const auto minFramePeriod = minFramePeriodLocked().ns(); @@ -436,20 +442,17 @@ Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentT if (!mPastExpectedPresentTimes.empty()) { const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime); if (phase > 0ns) { - for (auto& timeline : mTimelines) { - timeline.shiftVsyncSequence(phase); + if (mLastVsyncSequence) { + mLastVsyncSequence->vsyncTime += phase.ns(); } mPastExpectedPresentTimes.clear(); - return phase; } } - - return 0ns; } void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) { - ATRACE_NAME("VSyncPredictor::onFrameBegin"); + ATRACE_CALL(); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -479,14 +482,11 @@ void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, } } - const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); - if (phase > 0ns) { - mMissedVsync = {expectedPresentTime, minFramePeriodLocked()}; - } + ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); } void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { - ATRACE_NAME("VSyncPredictor::onFrameMissed"); + ATRACE_CALL(); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -496,15 +496,14 @@ void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { const auto lastConfirmedPresentTime = TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod); - const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); - if (phase > 0ns) { - mMissedVsync = {expectedPresentTime, Duration::fromNs(0)}; - } + ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + mLastMissedVsync = expectedPresentTime; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { std::lock_guard lock(mMutex); - return VSyncPredictor::getVSyncPredictionModelLocked(); + const auto model = VSyncPredictor::getVSyncPredictionModelLocked(); + return {model.slope, model.intercept}; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { @@ -525,11 +524,6 @@ void VSyncPredictor::clearTimestamps() { mTimestamps.clear(); mLastTimestampIndex = 0; } - - mTimelines.clear(); - mLastCommittedVsync = TimePoint::fromNs(0); - mIdealPeriod = Period::fromNs(idealPeriod()); - mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt); } bool VSyncPredictor::needsMoreSamples() const { @@ -553,130 +547,6 @@ void VSyncPredictor::dump(std::string& result) const { period / 1e6f, periodInterceptTuple.slope / 1e6f, periodInterceptTuple.intercept); } - StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size()); -} - -void VSyncPredictor::purgeTimelines(android::TimePoint now) { - while (mTimelines.size() > 1) { - const auto validUntilOpt = mTimelines.front().validUntil(); - if (validUntilOpt && *validUntilOpt < now) { - mTimelines.pop_front(); - } else { - break; - } - } - LOG_ALWAYS_FATAL_IF(mTimelines.empty()); - LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value()); -} - -VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional renderRateOpt) - : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {} - -void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) { - LOG_ALWAYS_FATAL_IF(mValidUntil.has_value()); - ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f", - mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA", - float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f); - mValidUntil = lastVsync; -} - -std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom( - Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync, - std::optional lastVsyncOpt) { - ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA"); - - const auto threshold = model.slope / 2; - const auto lastFrameMissed = - lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; - nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); - nsecs_t vsyncFixupTime = 0; - if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { - vsyncTime += missedVsync.fixup.ns(); - ATRACE_FORMAT_INSTANT("lastFrameMissed"); - } else { - vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt); - vsyncTime += vsyncFixupTime; - } - - ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f); - if (mValidUntil && vsyncTime > mValidUntil->ns()) { - ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f); - return std::nullopt; - } - - if (vsyncFixupTime > 0) { - shiftVsyncSequence(Duration::fromNs(vsyncFixupTime)); - } - - return TimePoint::fromNs(vsyncTime); -} - -auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync) - -> VsyncSequence { - if (!mLastVsyncSequence) return {vsync, 0}; - - const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; - const auto vsyncSequence = lastVsyncSequence + - static_cast(std::round((vsync - lastVsyncTime) / - static_cast(model.slope))); - return {vsync, vsyncSequence}; -} - -nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model, - nsecs_t vsync) { - // update the mLastVsyncSequence for reference point - mLastVsyncSequence = getVsyncSequenceLocked(model, vsync); - - const auto renderRatePhase = [&]() -> int { - if (!mRenderRateOpt) return 0; - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()), - *mRenderRateOpt); - if (divisor <= 1) return 0; - - int mod = mLastVsyncSequence->seq % divisor; - if (mod == 0) return 0; - - // This is actually a bug fix, but guarded with vrr_config since we found it with this - // config - if (FlagManager::getInstance().vrr_config()) { - if (mod < 0) mod += divisor; - } - - return divisor - mod; - }(); - - if (renderRatePhase == 0) { - return mLastVsyncSequence->vsyncTime; - } - - return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase; -} - -bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) { - const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float { - return ticks(TimePoint::fromNs(timePoint) - now); - }; - - Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns()); - const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate); - const auto now = TimePoint::now(); - - if (divisor <= 1) { - return true; - } - const auto vsyncSequence = getVsyncSequenceLocked(model, vsync); - ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu", - getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor); - return vsyncSequence.seq % divisor == 0; -} - -void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) { - if (mLastVsyncSequence) { - ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast(phase.ns()) / 1e6f); - mLastVsyncSequence->vsyncTime += phase.ns(); - } } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index c175765485..8fd7e6046d 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -22,7 +22,6 @@ #include #include -#include #include #include "VSyncTracker.h" @@ -32,7 +31,6 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* - * \param [in] Clock The clock abstraction. Useful for unit tests. * \param [in] PhysicalDisplayid The display this corresponds to. * \param [in] modePtr The initial display mode * \param [in] historySize The internal amount of entries to store in the model. @@ -40,13 +38,13 @@ public: * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(std::unique_ptr, ftl::NonNull modePtr, size_t historySize, + VSyncPredictor(ftl::NonNull modePtr, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt = {}) final + std::optional lastVsyncOpt = {}) const final EXCLUDES(mMutex); nsecs_t currentPeriod() const final EXCLUDES(mMutex); Period minFramePeriod() const final EXCLUDES(mMutex); @@ -64,7 +62,7 @@ public: VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex); - bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex); + bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); void setDisplayModePtr(ftl::NonNull) final EXCLUDES(mMutex); @@ -77,42 +75,10 @@ public: void dump(std::string& result) const final EXCLUDES(mMutex); private: - struct VsyncSequence { - nsecs_t vsyncTime; - int64_t seq; - }; - - struct MissedVsync { - TimePoint vsync; - Duration fixup = Duration::fromNs(0); - }; - - class VsyncTimeline { - public: - VsyncTimeline(Period idealPeriod, std::optional renderRateOpt); - std::optional nextAnticipatedVSyncTimeFrom( - Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, - std::optional lastVsyncOpt = {}); - void freeze(TimePoint lastVsync); - std::optional validUntil() const { return mValidUntil; } - bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate); - void shiftVsyncSequence(Duration phase); - - private: - nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); - VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync); - - const Period mIdealPeriod = Duration::fromNs(0); - const std::optional mRenderRateOpt; - std::optional mValidUntil; - std::optional mLastVsyncSequence; - }; - VSyncPredictor(VSyncPredictor const&) = delete; VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); - const std::unique_ptr mClock; const PhysicalDisplayId mId; inline void traceInt64If(const char* name, int64_t value) const; @@ -122,10 +88,16 @@ private: bool validate(nsecs_t timestamp) const REQUIRES(mMutex); Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex); + nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex); + bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); Period minFramePeriodLocked() const REQUIRES(mMutex); - Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); - void purgeTimelines(android::TimePoint now) REQUIRES(mMutex); + void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); nsecs_t idealPeriod() const REQUIRES(mMutex); bool const mTraceOn; @@ -143,15 +115,13 @@ private: std::vector mTimestamps GUARDED_BY(mMutex); ftl::NonNull mDisplayModePtr GUARDED_BY(mMutex); + std::optional mRenderRateOpt GUARDED_BY(mMutex); - std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); + mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); - MissedVsync mMissedVsync GUARDED_BY(mMutex); + std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); - std::deque mTimelines GUARDED_BY(mMutex); - TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0); - Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0); - std::optional mRenderRateOpt GUARDED_BY(mMutex); + TimePoint mLastMissedVsync GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 1e55a87254..37bd4b4977 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -56,8 +56,8 @@ public: * and avoid crossing the minimal frame period of a VRR display. * \return A prediction of the timestamp of a vsync event. */ - virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt = {}) = 0; + virtual nsecs_t nextAnticipatedVSyncTimeFrom( + nsecs_t timePoint, std::optional lastVsyncOpt = {}) const = 0; /* * The current period of the vsync signal. @@ -82,7 +82,7 @@ public: * \param [in] timePoint A vsync timestamp * \param [in] frameRate The frame rate to check for */ - virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0; + virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; /* * Sets the active mode of the display which includes the vsync period and other VRR attributes. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 2fa3318560..001938c756 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -120,8 +120,8 @@ VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull(std::make_unique(), modePtr, kHistorySize, - kMinSamplesForPrediction, kDiscardOutlierPercent); + return std::make_unique(modePtr, kHistorySize, kMinSamplesForPrediction, + kDiscardOutlierPercent); } VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 881d6789b2..85cd3e7c31 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -81,7 +81,7 @@ public: bool addResyncSample(TimePoint timestamp, ftl::Optional hwcVsyncPeriod); // TODO(b/185535769): Hide behind API. - VsyncTracker& getTracker() const { return *mTracker; } + const VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9e131447a5..cf5f55d7bd 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4702,14 +4702,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin return TransactionReadiness::NotReady; } - const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId}; - - // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued - // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again - // when applying the transaction. Otherwise we might throttle older transactions - // incorrectly as the frame rate of SF changed before it drained the older transactions. - if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID && - !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { + if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime, transaction.originUid); return TransactionReadiness::NotReady; @@ -4717,7 +4710,8 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin // If the client didn't specify desiredPresentTime, use the vsyncId to determine the // expected present time of this transaction. - if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) { + if (transaction.isAutoTimestamp && + frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64, transaction.frameTimelineInfo.vsyncId, expectedPresentTime); return TransactionReadiness::NotReady; diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 049b0923a6..10e2220ece 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -25,7 +25,6 @@ #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncPredictor.h" -#include "Scheduler/VSyncReactor.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockDisplayMode.h" @@ -564,8 +563,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { hal::VrrConfig{.minFrameIntervalNs = static_cast( frameRate.getPeriodNsecs())})); std::shared_ptr vrrTracker = - std::make_shared(std::make_unique(), kMode, kHistorySize, - kMinimumSamplesForPrediction, + std::make_shared(kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr vrrSelectorPtr = std::make_shared(makeModes(kMode), kMode->getId()); @@ -580,8 +578,6 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); vrrTracker->addVsyncTimestamp(0); - // Set 1000 as vsync seq #0 - vrrTracker->nextAnticipatedVSyncTimeFrom(700); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), @@ -591,7 +587,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { TimePoint::fromNs(2000))); // Not crossing the min frame period - EXPECT_EQ(Fps::fromPeriodNsecs(1000), + EXPECT_EQ(Fps::fromPeriodNsecs(1500), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2500))); // Change render rate @@ -599,9 +595,6 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); - // Set 2000 as vsync seq #0 - vrrTracker->nextAnticipatedVSyncTimeFrom(1700); - EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2000))); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index c22deaba72..d891008683 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -48,7 +48,7 @@ public: Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); } void resetModel() final {} bool needsMoreSamples() const final { return false; } - bool isVSyncInPhase(nsecs_t, Fps) final { return false; } + bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } void setDisplayModePtr(ftl::NonNull) final {} void setRenderRate(Fps) final {} void onFrameBegin(TimePoint, TimePoint) final {} @@ -64,7 +64,7 @@ class FixedRateIdealStubTracker : public StubTracker { public: FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) const final { auto const floor = timePoint % mPeriod; if (floor == 0) { return timePoint; @@ -77,7 +77,7 @@ class VRRStubTracker : public StubTracker { public: VRRStubTracker(nsecs_t period) : StubTracker(period) {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) const final { std::lock_guard lock(mMutex); auto const normalized_to_base = time_point - mBase; auto const floor = (normalized_to_base) % mPeriod; diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 6b9ea56062..b9f3d70c6b 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -75,28 +75,6 @@ ftl::NonNull displayMode(nsecs_t period) { return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution, DEFAULT_DISPLAY_ID)); } - -class TestClock : public Clock { -public: - TestClock() = default; - - nsecs_t now() const override { return mNow; } - void setNow(nsecs_t now) { mNow = now; } - -private: - nsecs_t mNow = 0; -}; - -class ClockWrapper : public Clock { -public: - ClockWrapper(std::shared_ptr const& clock) : mClock(clock) {} - - nsecs_t now() const { return mClock->now(); } - -private: - std::shared_ptr const mClock; -}; - } // namespace struct VSyncPredictorTest : testing::Test { @@ -108,10 +86,8 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - std::shared_ptr mClock{std::make_shared()}; - - VSyncPredictor tracker{std::make_unique(mClock), mMode, kHistorySize, - kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction, + kOutlierTolerancePercent}; }; TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) { @@ -432,8 +408,7 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { const auto mode = displayMode(mPeriod); - VSyncPredictor tracker{std::make_unique(mClock), mode, 20, - kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, @@ -631,6 +606,35 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } +TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); + + tracker.setRenderRate(refreshRate / 4); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 2); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 6); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); +} + TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { @@ -666,8 +670,8 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { .setVrrConfig(std::move(vrrConfig)) .build()); - VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, - kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction, + kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate); vrrTracker.addVsyncTimestamp(0); @@ -683,44 +687,7 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { vrrTracker.onFrameMissed(TimePoint::fromNs(4500)); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); - - vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500)); - EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000)); -} - -TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { - tracker.addVsyncTimestamp(1000); - - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - - tracker.setRenderRate(Fps::fromPeriodNsecs(2000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); - - tracker.setRenderRate(Fps::fromPeriodNsecs(3000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000)); - - // Check the purge logic works - mClock->setNow(20000); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000)); } - } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 6d10a5cc5d..3870983133 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -29,12 +29,12 @@ public: MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override)); MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional), - (override)); + (const, override)); MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override)); MOCK_METHOD(Period, minFramePeriod, (), (const, override)); MOCK_METHOD(void, resetModel, (), (override)); MOCK_METHOD(bool, needsMoreSamples, (), (const, override)); - MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override)); + MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override)); MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull), (override)); MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); -- GitLab From ffc31d175922da6c0f5d4b1aba30b30ba51dc0cc Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Wed, 28 Feb 2024 16:51:28 +0000 Subject: [PATCH 009/465] Add ADPF FMQ flag to SF FlagManager Bug: 315894228 Test: manual Change-Id: Ie33ed642451daea786ea0f3efcfd96792487625f --- services/surfaceflinger/Android.bp | 4 +-- .../CompositionEngine/Android.bp | 29 +++++++------------ services/surfaceflinger/Scheduler/Android.bp | 9 +++--- services/surfaceflinger/common/Android.bp | 26 +++++++++++++++++ .../surfaceflinger/common/FlagManager.cpp | 18 ++++++++---- .../common/include/common/FlagManager.h | 1 + services/surfaceflinger/tests/Android.bp | 2 +- .../surfaceflinger/tests/unittests/Android.bp | 4 +-- 8 files changed, 58 insertions(+), 35 deletions(-) diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 6a0ea221cc..3f68919875 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -42,6 +42,7 @@ cc_defaults { "android.hardware.power-ndk_shared", "librenderengine_deps", "libtimestats_deps", + "libsurfaceflinger_common_deps", "surfaceflinger_defaults", ], cflags: [ @@ -82,7 +83,6 @@ cc_defaults { "libinput", "libutils", "libSurfaceFlingerProp", - "server_configurable_flags", ], static_libs: [ "libaidlcommonsupport", @@ -95,10 +95,8 @@ cc_defaults { "libscheduler", "libserviceutils", "libshaders", - "libsurfaceflinger_common", "libtimestats", "libtonemap", - "libsurfaceflingerflags", ], header_libs: [ "android.hardware.graphics.composer@2.1-command-buffer", diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp index 0b01c666a4..a52cc87e7b 100644 --- a/services/surfaceflinger/CompositionEngine/Android.bp +++ b/services/surfaceflinger/CompositionEngine/Android.bp @@ -38,7 +38,6 @@ cc_defaults { "libSurfaceFlingerProp", "libui", "libutils", - "server_configurable_flags", ], static_libs: [ "liblayers_proto", @@ -90,24 +89,23 @@ filegroup { cc_library { name: "libcompositionengine", - defaults: ["libcompositionengine_defaults"], - static_libs: [ - "libsurfaceflinger_common", - "libsurfaceflingerflags", + defaults: [ + "libcompositionengine_defaults", + "libsurfaceflinger_common_deps", ], srcs: [ ":libcompositionengine_sources", ], local_include_dirs: ["include"], export_include_dirs: ["include"], - shared_libs: [ - "server_configurable_flags", - ], } cc_library { name: "libcompositionengine_mocks", - defaults: ["libcompositionengine_defaults"], + defaults: [ + "libcompositionengine_defaults", + "libsurfaceflinger_common_test_deps", + ], srcs: [ "mock/CompositionEngine.cpp", "mock/Display.cpp", @@ -123,11 +121,6 @@ cc_library { "libgtest", "libgmock", "libcompositionengine", - "libsurfaceflinger_common_test", - "libsurfaceflingerflags_test", - ], - shared_libs: [ - "server_configurable_flags", ], local_include_dirs: ["include"], export_include_dirs: ["include"], @@ -140,7 +133,10 @@ cc_test { "frameworks/native/services/surfaceflinger/common/include", "frameworks/native/services/surfaceflinger/tests/unittests", ], - defaults: ["libcompositionengine_defaults"], + defaults: [ + "libcompositionengine_defaults", + "libsurfaceflinger_common_test_deps", + ], srcs: [ ":libcompositionengine_sources", "tests/planner/CachedSetTest.cpp", @@ -166,14 +162,11 @@ cc_test { "librenderengine_mocks", "libgmock", "libgtest", - "libsurfaceflinger_common_test", - "libsurfaceflingerflags_test", ], shared_libs: [ // For some reason, libvulkan isn't picked up from librenderengine // Probably ASAN related? "libvulkan", - "server_configurable_flags", ], sanitize: { hwaddress: true, diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp index 16776cf18a..5455fdc155 100644 --- a/services/surfaceflinger/Scheduler/Android.bp +++ b/services/surfaceflinger/Scheduler/Android.bp @@ -53,7 +53,10 @@ cc_library_static { cc_test { name: "libscheduler_test", test_suites: ["device-tests"], - defaults: ["libscheduler_defaults"], + defaults: [ + "libscheduler_defaults", + "libsurfaceflinger_common_test_deps", + ], srcs: [ "tests/FrameTargeterTest.cpp", "tests/PresentLatencyTrackerTest.cpp", @@ -63,9 +66,5 @@ cc_test { "libgmock", "libgtest", "libscheduler", - "libsurfaceflingerflags_test", - ], - shared_libs: [ - "server_configurable_flags", ], } diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp index f2ff00b842..4a89dd06ce 100644 --- a/services/surfaceflinger/common/Android.bp +++ b/services/surfaceflinger/common/Android.bp @@ -35,6 +35,7 @@ cc_library_static { ], static_libs: [ "libsurfaceflingerflags", + "android.os.flags-aconfig-cc", ], } @@ -45,5 +46,30 @@ cc_library_static { ], static_libs: [ "libsurfaceflingerflags_test", + "android.os.flags-aconfig-cc-test", + ], +} + +cc_defaults { + name: "libsurfaceflinger_common_deps", + shared_libs: [ + "server_configurable_flags", + ], + static_libs: [ + "libsurfaceflinger_common", + "libsurfaceflingerflags", + "android.os.flags-aconfig-cc", + ], +} + +cc_defaults { + name: "libsurfaceflinger_common_test_deps", + shared_libs: [ + "server_configurable_flags", + ], + static_libs: [ + "libsurfaceflinger_common_test", + "libsurfaceflingerflags_test", + "android.os.flags-aconfig-cc-test", ], } diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index b7f06a992e..3b669c6b54 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace android { @@ -109,6 +110,7 @@ void FlagManager::dump(std::string& result) const { /// Trunk stable server flags /// DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display); + DUMP_SERVER_FLAG(adpf_use_fmq_channel); /// Trunk stable readonly flags /// DUMP_READ_ONLY_FLAG(connected_display); @@ -158,7 +160,7 @@ bool FlagManager::getServerConfigurableFlag(const char* experimentFlagName) cons return getServerConfigurableFlag(serverFlagName); \ } -#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted) \ +#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted, owner) \ bool FlagManager::name() const { \ if (checkForBootCompleted) { \ LOG_ALWAYS_FATAL_IF(!mBootCompleted, \ @@ -166,21 +168,24 @@ bool FlagManager::getServerConfigurableFlag(const char* experimentFlagName) cons __func__); \ } \ static const std::optional debugOverride = getBoolProperty(syspropOverride); \ - static const bool value = getFlagValue([] { return flags::name(); }, debugOverride); \ + static const bool value = getFlagValue([] { return owner ::name(); }, debugOverride); \ if (mUnitTestMode) { \ /* \ * When testing, we don't want to rely on the cached `value` or the debugOverride. \ */ \ - return flags::name(); \ + return owner ::name(); \ } \ return value; \ } #define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \ - FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true) + FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, flags) #define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \ - FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false) + FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, flags) + +#define FLAG_MANAGER_SERVER_FLAG_IMPORTED(name, syspropOverride, owner) \ + FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, owner) /// Legacy server flags /// FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "") @@ -216,4 +221,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "") /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") +/// Trunk stable server flags from outside SurfaceFlinger /// +FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os) + } // namespace android diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 241c814780..763963e24f 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -49,6 +49,7 @@ public: /// Trunk stable server flags /// bool refresh_rate_overlay_on_external_display() const; + bool adpf_use_fmq_channel() const; /// Trunk stable readonly flags /// bool connected_display() const; diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index dab0a3f13c..925fe0b794 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -27,6 +27,7 @@ cc_test { defaults: [ "android.hardware.graphics.common-ndk_shared", "surfaceflinger_defaults", + "libsurfaceflinger_common_test_deps", ], test_suites: ["device-tests"], srcs: [ @@ -66,7 +67,6 @@ cc_test { static_libs: [ "liblayers_proto", "android.hardware.graphics.composer@2.1", - "libsurfaceflingerflags", ], shared_libs: [ "android.hardware.graphics.common@1.2", diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index f529f7cb05..ca8a8172d2 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -149,6 +149,7 @@ cc_defaults { "android.hardware.graphics.composer3-ndk_static", "android.hardware.power-ndk_static", "librenderengine_deps", + "libsurfaceflinger_common_test_deps", ], static_libs: [ "android.hardware.common-V2-ndk", @@ -173,13 +174,11 @@ cc_defaults { "librenderengine_mocks", "libscheduler", "libserviceutils", - "libsurfaceflinger_common_test", "libtimestats", "libtimestats_atoms_proto", "libtimestats_proto", "libtonemap", "perfetto_trace_protos", - "libsurfaceflingerflags_test", ], shared_libs: [ "android.hardware.configstore-utils", @@ -208,7 +207,6 @@ cc_defaults { "libsync", "libui", "libutils", - "server_configurable_flags", ], header_libs: [ "android.hardware.graphics.composer3-command-buffer", -- GitLab From 6c18e6da2aa9758eb7c9a639f44a15a45723f65b Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Wed, 7 Feb 2024 23:39:50 +0000 Subject: [PATCH 010/465] Update PowerHAL wrapper support checking behavior - Updates support checks to check status for UNKNOWN_TRANSACTION - Adds PowerHintSessionWrapper class to check support on session methods - Ensures that wrapper methods check the HAL version number for support - Adds macros to cache returned wrapper call support status Bug: 324255931 Test: atest libpowermanager_test Test: atest libsurfaceflinger_unittest:PowerAdvisorTest Change-Id: I4b329e6b55c53198bb064a34e792be6336e66e27 --- include/powermanager/HalResult.h | 165 ++++++++++++++++ include/powermanager/PowerHalController.h | 17 +- include/powermanager/PowerHalLoader.h | 4 + include/powermanager/PowerHalWrapper.h | 180 +++--------------- .../powermanager/PowerHintSessionWrapper.h | 54 ++++++ services/powermanager/Android.bp | 1 + services/powermanager/PowerHalController.cpp | 57 ++++-- services/powermanager/PowerHalLoader.cpp | 7 + services/powermanager/PowerHalWrapper.cpp | 38 ++-- .../powermanager/PowerHintSessionWrapper.cpp | 80 ++++++++ services/powermanager/tests/Android.bp | 1 + .../tests/PowerHalWrapperAidlTest.cpp | 40 +++- .../tests/PowerHintSessionWrapperTest.cpp | 140 ++++++++++++++ .../DisplayHardware/PowerAdvisor.cpp | 5 +- .../DisplayHardware/PowerAdvisor.h | 4 +- .../surfaceflinger/tests/unittests/Android.bp | 2 +- .../tests/unittests/PowerAdvisorTest.cpp | 44 +++-- .../DisplayHardware/MockIPowerHintSession.h | 58 ------ .../DisplayHardware/MockPowerHalController.h | 4 +- ...on.cpp => MockPowerHintSessionWrapper.cpp} | 7 +- .../MockPowerHintSessionWrapper.h | 55 ++++++ 21 files changed, 669 insertions(+), 294 deletions(-) create mode 100644 include/powermanager/HalResult.h create mode 100644 include/powermanager/PowerHintSessionWrapper.h create mode 100644 services/powermanager/PowerHintSessionWrapper.cpp create mode 100644 services/powermanager/tests/PowerHintSessionWrapperTest.cpp delete mode 100644 services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h rename services/surfaceflinger/tests/unittests/mock/DisplayHardware/{MockIPowerHintSession.cpp => MockPowerHintSessionWrapper.cpp} (75%) create mode 100644 services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h diff --git a/include/powermanager/HalResult.h b/include/powermanager/HalResult.h new file mode 100644 index 0000000000..7fe3822be0 --- /dev/null +++ b/include/powermanager/HalResult.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace android::power { + +static bool checkUnsupported(const ndk::ScopedAStatus& ndkStatus) { + return ndkStatus.getExceptionCode() == EX_UNSUPPORTED_OPERATION || + ndkStatus.getStatus() == STATUS_UNKNOWN_TRANSACTION; +} + +static bool checkUnsupported(const binder::Status& status) { + return status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION || + status.transactionError() == UNKNOWN_TRANSACTION; +} + +// Result of a call to the Power HAL wrapper, holding data if successful. +template +class HalResult { +public: + static HalResult ok(T&& value) { return HalResult(std::forward(value)); } + static HalResult ok(T& value) { return HalResult::ok(T{value}); } + static HalResult failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); } + static HalResult unsupported() { return HalResult("", /* unsupported= */ true); } + + static HalResult fromStatus(const binder::Status& status, T&& data) { + if (checkUnsupported(status)) { + return HalResult::unsupported(); + } + if (status.isOk()) { + return HalResult::ok(std::forward(data)); + } + return HalResult::failed(std::string(status.toString8().c_str())); + } + + static HalResult fromStatus(const binder::Status& status, T& data) { + return HalResult::fromStatus(status, T{data}); + } + + static HalResult fromStatus(const ndk::ScopedAStatus& ndkStatus, T&& data) { + if (checkUnsupported(ndkStatus)) { + return HalResult::unsupported(); + } + if (ndkStatus.isOk()) { + return HalResult::ok(std::forward(data)); + } + return HalResult::failed(std::string(ndkStatus.getDescription())); + } + + static HalResult fromStatus(const ndk::ScopedAStatus& ndkStatus, T& data) { + return HalResult::fromStatus(ndkStatus, T{data}); + } + + template + static HalResult fromReturn(hardware::Return& ret, T&& data) { + return ret.isOk() ? HalResult::ok(std::forward(data)) + : HalResult::failed(ret.description()); + } + + template + static HalResult fromReturn(hardware::Return& ret, T& data) { + return HalResult::fromReturn(ret, T{data}); + } + + template + static HalResult fromReturn(hardware::Return& ret, hardware::power::V1_0::Status status, + T&& data) { + return ret.isOk() ? HalResult::fromStatus(status, std::forward(data)) + : HalResult::failed(ret.description()); + } + + template + static HalResult fromReturn(hardware::Return& ret, hardware::power::V1_0::Status status, + T& data) { + return HalResult::fromReturn(ret, status, T{data}); + } + + // This will throw std::bad_optional_access if this result is not ok. + const T& value() const { return mValue.value(); } + bool isOk() const { return !mUnsupported && mValue.has_value(); } + bool isFailed() const { return !mUnsupported && !mValue.has_value(); } + bool isUnsupported() const { return mUnsupported; } + const char* errorMessage() const { return mErrorMessage.c_str(); } + +private: + std::optional mValue; + std::string mErrorMessage; + bool mUnsupported; + + explicit HalResult(T&& value) + : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {} + explicit HalResult(std::string errorMessage, bool unsupported) + : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {} +}; + +// Empty result +template <> +class HalResult { +public: + static HalResult ok() { return HalResult(); } + static HalResult failed(std::string msg) { return HalResult(std::move(msg)); } + static HalResult unsupported() { return HalResult(/* unsupported= */ true); } + + static HalResult fromStatus(const binder::Status& status) { + if (checkUnsupported(status)) { + return HalResult::unsupported(); + } + if (status.isOk()) { + return HalResult::ok(); + } + return HalResult::failed(std::string(status.toString8().c_str())); + } + + static HalResult fromStatus(const ndk::ScopedAStatus& ndkStatus) { + if (ndkStatus.isOk()) { + return HalResult::ok(); + } + if (checkUnsupported(ndkStatus)) { + return HalResult::unsupported(); + } + return HalResult::failed(ndkStatus.getDescription()); + } + + template + static HalResult fromReturn(hardware::Return& ret) { + return ret.isOk() ? HalResult::ok() : HalResult::failed(ret.description()); + } + + bool isOk() const { return !mUnsupported && !mFailed; } + bool isFailed() const { return !mUnsupported && mFailed; } + bool isUnsupported() const { return mUnsupported; } + const char* errorMessage() const { return mErrorMessage.c_str(); } + +private: + std::string mErrorMessage; + bool mFailed; + bool mUnsupported; + + explicit HalResult(bool unsupported = false) + : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {} + explicit HalResult(std::string errorMessage) + : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {} +}; +} // namespace android::power diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h index c50bc4a188..7e0bd5bedc 100644 --- a/include/powermanager/PowerHalController.h +++ b/include/powermanager/PowerHalController.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace android { @@ -38,6 +39,7 @@ public: virtual std::unique_ptr connect(); virtual void reset(); + virtual int32_t getAidlVersion(); }; // ------------------------------------------------------------------------------------------------- @@ -59,14 +61,13 @@ public: int32_t durationMs) override; virtual HalResult setMode(aidl::android::hardware::power::Mode mode, bool enabled) override; - virtual HalResult> - createHintSession(int32_t tgid, int32_t uid, const std::vector& threadIds, - int64_t durationNanos) override; - virtual HalResult> - createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector& threadIds, - int64_t durationNanos, - aidl::android::hardware::power::SessionTag tag, - aidl::android::hardware::power::SessionConfig* config) override; + virtual HalResult> createHintSession( + int32_t tgid, int32_t uid, const std::vector& threadIds, + int64_t durationNanos) override; + virtual HalResult> createHintSessionWithConfig( + int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) override; virtual HalResult getHintSessionPreferredRate() override; virtual HalResult getSessionChannel( int tgid, int uid) override; diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h index cbbfa597ba..ab66336738 100644 --- a/include/powermanager/PowerHalLoader.h +++ b/include/powermanager/PowerHalLoader.h @@ -36,6 +36,8 @@ public: static sp loadHidlV1_1(); static sp loadHidlV1_2(); static sp loadHidlV1_3(); + // Returns aidl interface version, or 0 if AIDL is not used + static int32_t getAidlVersion(); private: static std::mutex gHalMutex; @@ -48,6 +50,8 @@ private: static sp loadHidlV1_0Locked() EXCLUSIVE_LOCKS_REQUIRED(gHalMutex); + static int32_t gAidlInterfaceVersion; + PowerHalLoader() = delete; ~PowerHalLoader() = delete; }; diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h index e2da014606..6e347a9ce9 100644 --- a/include/powermanager/PowerHalWrapper.h +++ b/include/powermanager/PowerHalWrapper.h @@ -26,6 +26,9 @@ #include #include #include +#include +#include + #include #include @@ -41,134 +44,6 @@ enum class HalSupport { OFF = 2, }; -// Result of a call to the Power HAL wrapper, holding data if successful. -template -class HalResult { -public: - static HalResult ok(T&& value) { return HalResult(std::forward(value)); } - static HalResult ok(T& value) { return HalResult::ok(T{value}); } - static HalResult failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); } - static HalResult unsupported() { return HalResult("", /* unsupported= */ true); } - - static HalResult fromStatus(const binder::Status& status, T&& data) { - if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) { - return HalResult::unsupported(); - } - if (status.isOk()) { - return HalResult::ok(std::forward(data)); - } - return HalResult::failed(std::string(status.toString8().c_str())); - } - - static HalResult fromStatus(const binder::Status& status, T& data) { - return HalResult::fromStatus(status, T{data}); - } - - static HalResult fromStatus(const ndk::ScopedAStatus& status, T&& data) { - if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) { - return HalResult::unsupported(); - } - if (status.isOk()) { - return HalResult::ok(std::forward(data)); - } - return HalResult::failed(std::string(status.getDescription())); - } - - static HalResult fromStatus(const ndk::ScopedAStatus& status, T& data) { - return HalResult::fromStatus(status, T{data}); - } - - template - static HalResult fromReturn(hardware::Return& ret, T&& data) { - return ret.isOk() ? HalResult::ok(std::forward(data)) - : HalResult::failed(ret.description()); - } - - template - static HalResult fromReturn(hardware::Return& ret, T& data) { - return HalResult::fromReturn(ret, T{data}); - } - - template - static HalResult fromReturn(hardware::Return& ret, hardware::power::V1_0::Status status, - T&& data) { - return ret.isOk() ? HalResult::fromStatus(status, std::forward(data)) - : HalResult::failed(ret.description()); - } - - template - static HalResult fromReturn(hardware::Return& ret, hardware::power::V1_0::Status status, - T& data) { - return HalResult::fromReturn(ret, status, T{data}); - } - - // This will throw std::bad_optional_access if this result is not ok. - const T& value() const { return mValue.value(); } - bool isOk() const { return !mUnsupported && mValue.has_value(); } - bool isFailed() const { return !mUnsupported && !mValue.has_value(); } - bool isUnsupported() const { return mUnsupported; } - const char* errorMessage() const { return mErrorMessage.c_str(); } - -private: - std::optional mValue; - std::string mErrorMessage; - bool mUnsupported; - - explicit HalResult(T&& value) - : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {} - explicit HalResult(std::string errorMessage, bool unsupported) - : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {} -}; - -// Empty result of a call to the Power HAL wrapper. -template <> -class HalResult { -public: - static HalResult ok() { return HalResult(); } - static HalResult failed(std::string msg) { return HalResult(std::move(msg)); } - static HalResult unsupported() { return HalResult(/* unsupported= */ true); } - - static HalResult fromStatus(const binder::Status& status) { - if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) { - return HalResult::unsupported(); - } - if (status.isOk()) { - return HalResult::ok(); - } - return HalResult::failed(std::string(status.toString8().c_str())); - } - - static HalResult fromStatus(const ndk::ScopedAStatus& status) { - if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) { - return HalResult::unsupported(); - } - if (status.isOk()) { - return HalResult::ok(); - } - return HalResult::failed(std::string(status.getDescription())); - } - - template - static HalResult fromReturn(hardware::Return& ret) { - return ret.isOk() ? HalResult::ok() : HalResult::failed(ret.description()); - } - - bool isOk() const { return !mUnsupported && !mFailed; } - bool isFailed() const { return !mUnsupported && mFailed; } - bool isUnsupported() const { return mUnsupported; } - const char* errorMessage() const { return mErrorMessage.c_str(); } - -private: - std::string mErrorMessage; - bool mFailed; - bool mUnsupported; - - explicit HalResult(bool unsupported = false) - : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {} - explicit HalResult(std::string errorMessage) - : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {} -}; - // Wrapper for Power HAL handlers. class HalWrapper { public: @@ -177,14 +52,13 @@ public: virtual HalResult setBoost(aidl::android::hardware::power::Boost boost, int32_t durationMs) = 0; virtual HalResult setMode(aidl::android::hardware::power::Mode mode, bool enabled) = 0; - virtual HalResult> - createHintSession(int32_t tgid, int32_t uid, const std::vector& threadIds, - int64_t durationNanos) = 0; - virtual HalResult> - createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector& threadIds, - int64_t durationNanos, - aidl::android::hardware::power::SessionTag tag, - aidl::android::hardware::power::SessionConfig* config) = 0; + virtual HalResult> createHintSession( + int32_t tgid, int32_t uid, const std::vector& threadIds, + int64_t durationNanos) = 0; + virtual HalResult> createHintSessionWithConfig( + int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) = 0; virtual HalResult getHintSessionPreferredRate() = 0; virtual HalResult getSessionChannel(int tgid, int uid) = 0; @@ -200,14 +74,13 @@ public: HalResult setBoost(aidl::android::hardware::power::Boost boost, int32_t durationMs) override; HalResult setMode(aidl::android::hardware::power::Mode mode, bool enabled) override; - HalResult> createHintSession( + HalResult> createHintSession( int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos) override; - HalResult> - createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector& threadIds, - int64_t durationNanos, - aidl::android::hardware::power::SessionTag tag, - aidl::android::hardware::power::SessionConfig* config) override; + HalResult> createHintSessionWithConfig( + int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) override; HalResult getHintSessionPreferredRate() override; HalResult getSessionChannel(int tgid, int uid) override; @@ -285,14 +158,13 @@ public: HalResult setBoost(aidl::android::hardware::power::Boost boost, int32_t durationMs) override; HalResult setMode(aidl::android::hardware::power::Mode mode, bool enabled) override; - HalResult> createHintSession( + HalResult> createHintSession( int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos) override; - HalResult> - createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector& threadIds, - int64_t durationNanos, - aidl::android::hardware::power::SessionTag tag, - aidl::android::hardware::power::SessionConfig* config) override; + HalResult> createHintSessionWithConfig( + int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, + aidl::android::hardware::power::SessionTag tag, + aidl::android::hardware::power::SessionConfig* config) override; HalResult getHintSessionPreferredRate() override; HalResult getSessionChannel(int tgid, @@ -307,14 +179,12 @@ private: std::mutex mBoostMutex; std::mutex mModeMutex; std::shared_ptr mHandle; - // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT. - // Need to increase the array size if more boost supported. - std::array< - std::atomic, - static_cast(aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) + - 1> + std::array( + *(ndk::enum_range().end() - 1)) + + 1> mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN}; - std::array, + std::array( *(ndk::enum_range().end() - 1)) + 1> diff --git a/include/powermanager/PowerHintSessionWrapper.h b/include/powermanager/PowerHintSessionWrapper.h new file mode 100644 index 0000000000..ba6fe77c80 --- /dev/null +++ b/include/powermanager/PowerHintSessionWrapper.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "HalResult.h" + +namespace android::power { + +// Wrapper for power hint sessions, which allows for better mocking, +// support checking, and failure handling than using hint sessions directly +class PowerHintSessionWrapper { +public: + virtual ~PowerHintSessionWrapper() = default; + PowerHintSessionWrapper( + std::shared_ptr&& session); + virtual HalResult updateTargetWorkDuration(int64_t in_targetDurationNanos); + virtual HalResult reportActualWorkDuration( + const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations); + virtual HalResult pause(); + virtual HalResult resume(); + virtual HalResult close(); + virtual HalResult sendHint(::aidl::android::hardware::power::SessionHint in_hint); + virtual HalResult setThreads(const std::vector& in_threadIds); + virtual HalResult setMode(::aidl::android::hardware::power::SessionMode in_type, + bool in_enabled); + virtual HalResult getSessionConfig(); + +private: + std::shared_ptr mSession; + int32_t mInterfaceVersion; +}; + +} // namespace android::power \ No newline at end of file diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp index 1f72e8ba2c..3ea08febfd 100644 --- a/services/powermanager/Android.bp +++ b/services/powermanager/Android.bp @@ -17,6 +17,7 @@ cc_library_shared { "PowerHalController.cpp", "PowerHalLoader.cpp", "PowerHalWrapper.cpp", + "PowerHintSessionWrapper.cpp", "PowerSaveState.cpp", "Temperature.cpp", "WorkDuration.cpp", diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp index bc178bce8f..40fd097491 100644 --- a/services/powermanager/PowerHalController.cpp +++ b/services/powermanager/PowerHalController.cpp @@ -57,6 +57,10 @@ void HalConnector::reset() { PowerHalLoader::unloadAll(); } +int32_t HalConnector::getAidlVersion() { + return PowerHalLoader::getAidlVersion(); +} + // ------------------------------------------------------------------------------------------------- void PowerHalController::init() { @@ -77,6 +81,22 @@ std::shared_ptr PowerHalController::initHal() { return mConnectedHal; } +// Using statement expression macro instead of a method lets the static be +// scoped to the outer method while dodging the need for a support lookup table +// This only works for AIDL methods that do not vary supported/unsupported depending +// on their arguments (not setBoost, setMode) which do their own support checks +#define CACHE_SUPPORT(version, method) \ + ({ \ + static bool support = mHalConnector->getAidlVersion() >= version; \ + !support ? decltype(method)::unsupported() : ({ \ + auto result = method; \ + if (result.isUnsupported()) { \ + support = false; \ + } \ + std::move(result); \ + }); \ + }) + // Check if a call to Power HAL function failed; if so, log the failure and // invalidate the current Power HAL handle. template @@ -103,40 +123,49 @@ HalResult PowerHalController::setMode(aidl::android::hardware::power::Mode return processHalResult(handle->setMode(mode, enabled), "setMode"); } -HalResult> -PowerHalController::createHintSession(int32_t tgid, int32_t uid, - const std::vector& threadIds, - int64_t durationNanos) { +// Aidl-only methods + +HalResult> PowerHalController::createHintSession( + int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos) { std::shared_ptr handle = initHal(); - return processHalResult(handle->createHintSession(tgid, uid, threadIds, durationNanos), - "createHintSession"); + return CACHE_SUPPORT(2, + processHalResult(handle->createHintSession(tgid, uid, threadIds, + durationNanos), + "createHintSession")); } -HalResult> -PowerHalController::createHintSessionWithConfig( +HalResult> PowerHalController::createHintSessionWithConfig( int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, aidl::android::hardware::power::SessionTag tag, aidl::android::hardware::power::SessionConfig* config) { std::shared_ptr handle = initHal(); - return processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, - tag, config), - "createHintSessionWithConfig"); + return CACHE_SUPPORT(5, + processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds, + durationNanos, tag, + config), + "createHintSessionWithConfig")); } HalResult PowerHalController::getHintSessionPreferredRate() { std::shared_ptr handle = initHal(); - return processHalResult(handle->getHintSessionPreferredRate(), "getHintSessionPreferredRate"); + return CACHE_SUPPORT(2, + processHalResult(handle->getHintSessionPreferredRate(), + "getHintSessionPreferredRate")); } HalResult PowerHalController::getSessionChannel( int tgid, int uid) { std::shared_ptr handle = initHal(); - return processHalResult(handle->getSessionChannel(tgid, uid), "getSessionChannel"); + return CACHE_SUPPORT(5, + processHalResult(handle->getSessionChannel(tgid, uid), + "getSessionChannel")); } HalResult PowerHalController::closeSessionChannel(int tgid, int uid) { std::shared_ptr handle = initHal(); - return processHalResult(handle->closeSessionChannel(tgid, uid), "closeSessionChannel"); + return CACHE_SUPPORT(5, + processHalResult(handle->closeSessionChannel(tgid, uid), + "closeSessionChannel")); } } // namespace power diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp index 22144615da..ea284c36d8 100644 --- a/services/powermanager/PowerHalLoader.cpp +++ b/services/powermanager/PowerHalLoader.cpp @@ -60,6 +60,7 @@ sp PowerHalLoader::gHalHidlV1_0 = nullptr; sp PowerHalLoader::gHalHidlV1_1 = nullptr; sp PowerHalLoader::gHalHidlV1_2 = nullptr; sp PowerHalLoader::gHalHidlV1_3 = nullptr; +int32_t PowerHalLoader::gAidlInterfaceVersion = 0; void PowerHalLoader::unloadAll() { std::lock_guard lock(gHalMutex); @@ -89,6 +90,8 @@ std::shared_ptr PowerHalLoader::loadAidl ndk::SpAIBinder(AServiceManager_waitForService(aidlServiceName.c_str()))); if (gHalAidl) { ALOGI("Successfully connected to Power HAL AIDL service."); + gHalAidl->getInterfaceVersion(&gAidlInterfaceVersion); + } else { ALOGI("Power HAL AIDL service not available."); gHalExists = false; @@ -128,6 +131,10 @@ sp PowerHalLoader::loadHidlV1_0Locked() { return loadHal(gHalExists, gHalHidlV1_0, loadFn, "HIDL v1.0"); } +int32_t PowerHalLoader::getAidlVersion() { + return gAidlInterfaceVersion; +} + // ------------------------------------------------------------------------------------------------- } // namespace power diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp index 1009100cc2..bd6685cbad 100644 --- a/services/powermanager/PowerHalWrapper.cpp +++ b/services/powermanager/PowerHalWrapper.cpp @@ -18,11 +18,10 @@ #include #include #include +#include #include #include -#include - using namespace android::hardware::power; namespace Aidl = aidl::android::hardware::power; @@ -30,15 +29,6 @@ namespace android { namespace power { -// ------------------------------------------------------------------------------------------------- -inline HalResult toHalResult(const ndk::ScopedAStatus& result) { - if (result.isOk()) { - return HalResult::ok(); - } - ALOGE("Power HAL request failed: %s", result.getDescription().c_str()); - return HalResult::failed(result.getDescription()); -} - // ------------------------------------------------------------------------------------------------- HalResult EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) { @@ -53,19 +43,19 @@ HalResult EmptyHalWrapper::setMode(Aidl::Mode mode, bool enabled) { return HalResult::unsupported(); } -HalResult> EmptyHalWrapper::createHintSession( +HalResult> EmptyHalWrapper::createHintSession( int32_t, int32_t, const std::vector& threadIds, int64_t) { ALOGV("Skipped createHintSession(task num=%zu) because %s", threadIds.size(), getUnsupportedMessage()); - return HalResult>::unsupported(); + return HalResult>::unsupported(); } -HalResult> EmptyHalWrapper::createHintSessionWithConfig( +HalResult> EmptyHalWrapper::createHintSessionWithConfig( int32_t, int32_t, const std::vector& threadIds, int64_t, Aidl::SessionTag, Aidl::SessionConfig*) { ALOGV("Skipped createHintSessionWithConfig(task num=%zu) because %s", threadIds.size(), getUnsupportedMessage()); - return HalResult>::unsupported(); + return HalResult>::unsupported(); } HalResult EmptyHalWrapper::getHintSessionPreferredRate() { @@ -225,7 +215,7 @@ HalResult AidlHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) } lock.unlock(); - return toHalResult(mHandle->setBoost(boost, durationMs)); + return HalResult::fromStatus(mHandle->setBoost(boost, durationMs)); } HalResult AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) { @@ -253,25 +243,25 @@ HalResult AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) { } lock.unlock(); - return toHalResult(mHandle->setMode(mode, enabled)); + return HalResult::fromStatus(mHandle->setMode(mode, enabled)); } -HalResult> AidlHalWrapper::createHintSession( +HalResult> AidlHalWrapper::createHintSession( int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos) { std::shared_ptr appSession; - return HalResult>:: + return HalResult>:: fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession), - std::move(appSession)); + std::make_shared(std::move(appSession))); } -HalResult> AidlHalWrapper::createHintSessionWithConfig( +HalResult> AidlHalWrapper::createHintSessionWithConfig( int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, Aidl::SessionTag tag, Aidl::SessionConfig* config) { std::shared_ptr appSession; - return HalResult>:: + return HalResult>:: fromStatus(mHandle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, config, &appSession), - std::move(appSession)); + std::make_shared(std::move(appSession))); } HalResult AidlHalWrapper::getHintSessionPreferredRate() { @@ -287,7 +277,7 @@ HalResult AidlHalWrapper::getSessionChannel(int tgid, int u } HalResult AidlHalWrapper::closeSessionChannel(int tgid, int uid) { - return toHalResult(mHandle->closeSessionChannel(tgid, uid)); + return HalResult::fromStatus(mHandle->closeSessionChannel(tgid, uid)); } const char* AidlHalWrapper::getUnsupportedMessage() { diff --git a/services/powermanager/PowerHintSessionWrapper.cpp b/services/powermanager/PowerHintSessionWrapper.cpp new file mode 100644 index 0000000000..930c7fa2b8 --- /dev/null +++ b/services/powermanager/PowerHintSessionWrapper.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 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. + */ + +#include + +using namespace aidl::android::hardware::power; + +namespace android::power { + +// Caches support for a given call in a static variable, checking both +// the return value and interface version. +#define CACHE_SUPPORT(version, method) \ + ({ \ + static bool support = mInterfaceVersion >= version; \ + !support ? decltype(method)::unsupported() : ({ \ + auto result = method; \ + if (result.isUnsupported()) { \ + support = false; \ + } \ + std::move(result); \ + }); \ + }) + +#define CHECK_SESSION(resultType) \ + if (mSession == nullptr) { \ + return HalResult::failed("Session not running"); \ + } + +// FWD_CALL just forwards calls from the wrapper to the session object. +// It only works if the call has no return object, as is the case with all calls +// except getSessionConfig. +#define FWD_CALL(version, name, args, untypedArgs) \ + HalResult PowerHintSessionWrapper::name args { \ + CHECK_SESSION(void) \ + return CACHE_SUPPORT(version, HalResult::fromStatus(mSession->name untypedArgs)); \ + } + +PowerHintSessionWrapper::PowerHintSessionWrapper(std::shared_ptr&& session) + : mSession(session) { + if (mSession != nullptr) { + mSession->getInterfaceVersion(&mInterfaceVersion); + } +} + +// Support for individual hints/modes is not really handled here since there +// is no way to check for it, so in the future if a way to check that is added, +// this will need to be updated. + +FWD_CALL(2, updateTargetWorkDuration, (int64_t in_targetDurationNanos), (in_targetDurationNanos)); +FWD_CALL(2, reportActualWorkDuration, (const std::vector& in_durations), + (in_durations)); +FWD_CALL(2, pause, (), ()); +FWD_CALL(2, resume, (), ()); +FWD_CALL(2, close, (), ()); +FWD_CALL(4, sendHint, (SessionHint in_hint), (in_hint)); +FWD_CALL(4, setThreads, (const std::vector& in_threadIds), (in_threadIds)); +FWD_CALL(5, setMode, (SessionMode in_type, bool in_enabled), (in_type, in_enabled)); + +HalResult PowerHintSessionWrapper::getSessionConfig() { + CHECK_SESSION(SessionConfig); + SessionConfig config; + return CACHE_SUPPORT(5, + HalResult::fromStatus(mSession->getSessionConfig(&config), + std::move(config))); +} + +} // namespace android::power diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp index 6fc96c0959..a05ce2b39c 100644 --- a/services/powermanager/tests/Android.bp +++ b/services/powermanager/tests/Android.bp @@ -37,6 +37,7 @@ cc_test { "PowerHalWrapperHidlV1_1Test.cpp", "PowerHalWrapperHidlV1_2Test.cpp", "PowerHalWrapperHidlV1_3Test.cpp", + "PowerHintSessionWrapperTest.cpp", "WorkSourceTest.cpp", ], cflags: [ diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp index a7202969ad..1589c9937d 100644 --- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp +++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp @@ -86,6 +86,10 @@ protected: void PowerHalWrapperAidlTest::SetUp() { mMockHal = ndk::SharedRefBase::make>(); + EXPECT_CALL(*mMockHal, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) { + *ret = 5; + return ndk::ScopedAStatus::ok(); + })); mWrapper = std::make_unique(mMockHal); ASSERT_NE(nullptr, mWrapper); } @@ -130,10 +134,12 @@ TEST_F(PowerHalWrapperAidlTest, TestSetBoostFailed) { } TEST_F(PowerHalWrapperAidlTest, TestSetBoostUnsupported) { - EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _)) - .Times(Exactly(1)) - .WillOnce(DoAll(SetArgPointee<1>(false), - Return(testing::ByMove(ndk::ScopedAStatus::ok())))); + EXPECT_CALL(*mMockHal.get(), isBoostSupported(_, _)) + .Times(Exactly(2)) + .WillRepeatedly([](Boost, bool* ret) { + *ret = false; + return ndk::ScopedAStatus::ok(); + }); auto result = mWrapper->setBoost(Boost::INTERACTION, 1000); ASSERT_TRUE(result.isUnsupported()); @@ -311,3 +317,29 @@ TEST_F(PowerHalWrapperAidlTest, TestSessionChannel) { auto closeResult = mWrapper->closeSessionChannel(tgid, uid); ASSERT_TRUE(closeResult.isOk()); } + +TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigUnsupported) { + std::vector threadIds{gettid()}; + int32_t tgid = 999; + int32_t uid = 1001; + int64_t durationNanos = 16666666L; + SessionTag tag = SessionTag::OTHER; + SessionConfig out; + EXPECT_CALL(*mMockHal.get(), + createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), + Eq(tag), _, _)) + .Times(1) + .WillOnce(Return(testing::ByMove( + ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))); + auto result = + mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out); + ASSERT_TRUE(result.isUnsupported()); + Mock::VerifyAndClearExpectations(mMockHal.get()); + EXPECT_CALL(*mMockHal.get(), + createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), + Eq(tag), _, _)) + .WillOnce(Return( + testing::ByMove(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)))); + result = mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out); + ASSERT_TRUE(result.isUnsupported()); +} diff --git a/services/powermanager/tests/PowerHintSessionWrapperTest.cpp b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp new file mode 100644 index 0000000000..7743fa4363 --- /dev/null +++ b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2024 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. + */ + +#include +#include + +#include +#include + +using aidl::android::hardware::power::IPowerHintSession; +using android::power::PowerHintSessionWrapper; + +using namespace android; +using namespace std::chrono_literals; +using namespace testing; + +class MockIPowerHintSession : public IPowerHintSession { +public: + MockIPowerHintSession() = default; + MOCK_METHOD(::ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t in_targetDurationNanos), + (override)); + MOCK_METHOD(::ndk::ScopedAStatus, reportActualWorkDuration, + (const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations), + (override)); + MOCK_METHOD(::ndk::ScopedAStatus, pause, (), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, resume, (), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, close, (), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, sendHint, + (::aidl::android::hardware::power::SessionHint in_hint), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, setThreads, (const std::vector& in_threadIds), + (override)); + MOCK_METHOD(::ndk::ScopedAStatus, setMode, + (::aidl::android::hardware::power::SessionMode in_type, bool in_enabled), + (override)); + MOCK_METHOD(::ndk::ScopedAStatus, getSessionConfig, + (::aidl::android::hardware::power::SessionConfig * _aidl_return), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override)); + MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override)); + MOCK_METHOD(::ndk::SpAIBinder, asBinder, (), (override)); + MOCK_METHOD(bool, isRemote, (), (override)); +}; + +class PowerHintSessionWrapperTest : public Test { +public: + void SetUp() override; + +protected: + std::shared_ptr> mMockSession = nullptr; + std::unique_ptr mSession = nullptr; +}; + +void PowerHintSessionWrapperTest::SetUp() { + mMockSession = ndk::SharedRefBase::make>(); + EXPECT_CALL(*mMockSession, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) { + *ret = 5; + return ndk::ScopedAStatus::ok(); + })); + mSession = std::make_unique(mMockSession); + ASSERT_NE(nullptr, mSession); +} + +TEST_F(PowerHintSessionWrapperTest, updateTargetWorkDuration) { + EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1000000000)) + .WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->updateTargetWorkDuration(1000000000); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, reportActualWorkDuration) { + EXPECT_CALL(*mMockSession.get(), + reportActualWorkDuration( + std::vector<::aidl::android::hardware::power::WorkDuration>())) + .WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->reportActualWorkDuration( + std::vector<::aidl::android::hardware::power::WorkDuration>()); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, pause) { + EXPECT_CALL(*mMockSession.get(), pause()).WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->pause(); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, resume) { + EXPECT_CALL(*mMockSession.get(), resume()).WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->resume(); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, close) { + EXPECT_CALL(*mMockSession.get(), close()).WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->close(); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, sendHint) { + EXPECT_CALL(*mMockSession.get(), + sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP)) + .WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, setThreads) { + EXPECT_CALL(*mMockSession.get(), setThreads(_)).WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->setThreads(std::vector{gettid()}); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, setMode) { + EXPECT_CALL(*mMockSession.get(), + setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY, true)) + .WillOnce(Return(ndk::ScopedAStatus::ok())); + auto status = mSession->setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY, + true); + ASSERT_TRUE(status.isOk()); +} + +TEST_F(PowerHintSessionWrapperTest, getSessionConfig) { + EXPECT_CALL(*mMockSession.get(), getSessionConfig(_)) + .WillOnce(DoAll(SetArgPointee<0>( + aidl::android::hardware::power::SessionConfig{.id = 12L}), + Return(ndk::ScopedAStatus::ok()))); + auto status = mSession->getSessionConfig(); + ASSERT_TRUE(status.isOk()); +} diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index a0c943ba72..8dfbeb80cd 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -233,7 +233,7 @@ void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) { auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns()); if (!ret.isOk()) { ALOGW("Failed to set power hint target work duration with error: %s", - ret.getDescription().c_str()); + ret.errorMessage()); mHintSession = nullptr; } } @@ -293,8 +293,7 @@ void PowerAdvisor::reportActualWorkDuration() { auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue); if (!ret.isOk()) { - ALOGW("Failed to report actual work durations with error: %s", - ret.getDescription().c_str()); + ALOGW("Failed to report actual work durations with error: %s", ret.errorMessage()); mHintSession = nullptr; return; } diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index bbe51cc09d..1040048b13 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -259,8 +259,8 @@ private: std::optional mSupportsHintSession; std::mutex mHintSessionMutex; - std::shared_ptr mHintSession - GUARDED_BY(mHintSessionMutex) = nullptr; + std::shared_ptr mHintSession GUARDED_BY(mHintSessionMutex) = + nullptr; // Initialize to true so we try to call, to check if it's supported bool mHasExpensiveRendering = true; diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index f529f7cb05..08cfc55bf9 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -29,7 +29,7 @@ filegroup { "mock/DisplayHardware/MockComposer.cpp", "mock/DisplayHardware/MockHWC2.cpp", "mock/DisplayHardware/MockIPower.cpp", - "mock/DisplayHardware/MockIPowerHintSession.cpp", + "mock/DisplayHardware/MockPowerHintSessionWrapper.cpp", "mock/DisplayHardware/MockPowerAdvisor.cpp", "mock/MockEventThread.cpp", "mock/MockFrameTimeline.cpp", diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index 9c66a97573..1d44a3ef77 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -26,8 +26,8 @@ #include #include #include "TestableSurfaceFlinger.h" -#include "mock/DisplayHardware/MockIPowerHintSession.h" #include "mock/DisplayHardware/MockPowerHalController.h" +#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h" using namespace android; using namespace android::Hwc2::mock; @@ -54,7 +54,7 @@ protected: TestableSurfaceFlinger mFlinger; std::unique_ptr mPowerAdvisor; MockPowerHalController* mMockPowerHalController; - std::shared_ptr mMockPowerHintSession; + std::shared_ptr mMockPowerHintSession; }; bool PowerAdvisorTest::sessionExists() { @@ -68,25 +68,29 @@ void PowerAdvisorTest::SetUp() { mMockPowerHalController = reinterpret_cast(mPowerAdvisor->mPowerHal.get()); ON_CALL(*mMockPowerHalController, getHintSessionPreferredRate) - .WillByDefault(Return(HalResult::fromStatus(binder::Status::ok(), 16000))); + .WillByDefault(Return( + ByMove(HalResult::fromStatus(ndk::ScopedAStatus::ok(), 16000)))); } void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) { - mMockPowerHintSession = ndk::SharedRefBase::make>(); + mMockPowerHintSession = std::make_shared>(); if (returnValidSession) { ON_CALL(*mMockPowerHalController, createHintSession) - .WillByDefault( - Return(HalResult>:: - fromStatus(binder::Status::ok(), mMockPowerHintSession))); + .WillByDefault([&](int32_t, int32_t, const std::vector&, int64_t) { + return HalResult>:: + fromStatus(ndk::ScopedAStatus::ok(), mMockPowerHintSession); + }); } else { - ON_CALL(*mMockPowerHalController, createHintSession) - .WillByDefault(Return(HalResult>:: - fromStatus(binder::Status::ok(), nullptr))); + ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] { + return HalResult< + std::shared_ptr>::fromStatus(ndk::ScopedAStatus::ok(), + nullptr); + }); } mPowerAdvisor->enablePowerHintSession(true); mPowerAdvisor->startPowerHintSession({1, 2, 3}); ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration) - .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + .WillByDefault(Return(testing::ByMove(HalResult::ok()))); } void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration, @@ -148,7 +152,7 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) { reportActualWorkDuration(ElementsAre( Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns()))))) .Times(1) - .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + .WillOnce(Return(testing::ByMove(HalResult::ok()))); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); mPowerAdvisor->setDisplays(displayIds); @@ -188,7 +192,7 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) { reportActualWorkDuration(ElementsAre( Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns()))))) .Times(1) - .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + .WillOnce(Return(testing::ByMove(HalResult::ok()))); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); @@ -231,7 +235,7 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) { reportActualWorkDuration(ElementsAre( Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns()))))) .Times(1) - .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok()))); + .WillOnce(Return(testing::ByMove(HalResult::ok()))); fakeBasicFrameTiming(startTime, vsyncPeriod); setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod); @@ -328,17 +332,17 @@ TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) { ON_CALL(*mMockPowerHintSession, sendHint).WillByDefault([&letSendHintFinish] { letSendHintFinish.get_future().wait(); - return ndk::ScopedAStatus::fromExceptionCode(-127); + return HalResult::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127)); }); ON_CALL(*mMockPowerHintSession, reportActualWorkDuration).WillByDefault([] { - return ndk::ScopedAStatus::fromExceptionCode(-127); + return HalResult::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127)); }); - ON_CALL(*mMockPowerHalController, createHintSession) - .WillByDefault(Return( - HalResult>:: - fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr))); + ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] { + return HalResult>:: + fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr); + }); // First background call, to notice the session is down auto firstHint = std::async(std::launch::async, [this] { diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h deleted file mode 100644 index 27564b26de..0000000000 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "binder/Status.h" - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" -#include -#pragma clang diagnostic pop - -#include - -using aidl::android::hardware::power::IPowerHintSession; -using aidl::android::hardware::power::SessionConfig; -using aidl::android::hardware::power::SessionHint; -using aidl::android::hardware::power::SessionMode; -using android::binder::Status; - -using namespace aidl::android::hardware::power; - -namespace android::Hwc2::mock { - -class MockIPowerHintSession : public IPowerHintSession { -public: - MockIPowerHintSession(); - - MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override)); - MOCK_METHOD(ndk::ScopedAStatus, pause, (), (override)); - MOCK_METHOD(ndk::ScopedAStatus, resume, (), (override)); - MOCK_METHOD(ndk::ScopedAStatus, close, (), (override)); - MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override)); - MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override)); - MOCK_METHOD(bool, isRemote, (), (override)); - MOCK_METHOD(ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t), (override)); - MOCK_METHOD(ndk::ScopedAStatus, reportActualWorkDuration, (const ::std::vector&), - (override)); - MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override)); - MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector&), (override)); - MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override)); - MOCK_METHOD(ndk::ScopedAStatus, getSessionConfig, (SessionConfig * _aidl_return), (override)); -}; - -} // namespace android::Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h index ae41e7ea75..af1862d1cf 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h @@ -44,10 +44,10 @@ public: MOCK_METHOD(HalResult, setBoost, (aidl::android::hardware::power::Boost, int32_t), (override)); MOCK_METHOD(HalResult, setMode, (aidl::android::hardware::power::Mode, bool), (override)); - MOCK_METHOD(HalResult>, + MOCK_METHOD(HalResult>, createHintSession, (int32_t, int32_t, const std::vector&, int64_t), (override)); - MOCK_METHOD(HalResult>, + MOCK_METHOD(HalResult>, createHintSessionWithConfig, (int32_t tgid, int32_t uid, const std::vector& threadIds, int64_t durationNanos, aidl::android::hardware::power::SessionTag tag, diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp similarity index 75% rename from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp rename to services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp index 770bc15869..d383283d8e 100644 --- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2024 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. @@ -14,11 +14,12 @@ * limitations under the License. */ -#include "mock/DisplayHardware/MockIPowerHintSession.h" +#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h" namespace android::Hwc2::mock { // Explicit default instantiation is recommended. -MockIPowerHintSession::MockIPowerHintSession() = default; +MockPowerHintSessionWrapper::MockPowerHintSessionWrapper() + : power::PowerHintSessionWrapper(nullptr) {} } // namespace android::Hwc2::mock diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h new file mode 100644 index 0000000000..bc6950cccb --- /dev/null +++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h @@ -0,0 +1,55 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "binder/Status.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wconversion" +#include +#include +#pragma clang diagnostic pop + +#include + +using aidl::android::hardware::power::IPowerHintSession; +using aidl::android::hardware::power::SessionConfig; +using aidl::android::hardware::power::SessionHint; +using aidl::android::hardware::power::SessionMode; +using android::binder::Status; + +using namespace aidl::android::hardware::power; + +namespace android::Hwc2::mock { + +class MockPowerHintSessionWrapper : public power::PowerHintSessionWrapper { +public: + MockPowerHintSessionWrapper(); + + MOCK_METHOD(power::HalResult, updateTargetWorkDuration, (int64_t), (override)); + MOCK_METHOD(power::HalResult, reportActualWorkDuration, + (const ::std::vector&), (override)); + MOCK_METHOD(power::HalResult, pause, (), (override)); + MOCK_METHOD(power::HalResult, resume, (), (override)); + MOCK_METHOD(power::HalResult, close, (), (override)); + MOCK_METHOD(power::HalResult, sendHint, (SessionHint), (override)); + MOCK_METHOD(power::HalResult, setThreads, (const ::std::vector&), (override)); + MOCK_METHOD(power::HalResult, setMode, (SessionMode, bool), (override)); + MOCK_METHOD(power::HalResult, getSessionConfig, (), (override)); +}; + +} // namespace android::Hwc2::mock -- GitLab From bf9b0a854f2351e337fc7c7658cc03b75055276b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 29 Feb 2024 02:23:50 +0000 Subject: [PATCH 011/465] Move MotionEvent#split implementation to native There is already a native implementation of split in InputDispatcher. Prevent code duplication by moving the Java impl to native. The Java impl is not correct, because it cannot access all values like the transforms to initialize the split event with. Bug: 326171104 Test: atest libinput_tests Test: atest inputflinger_tests Change-Id: I6230b6aa0696dcfc275a5a14ab4af3d4b7bd0b45 --- include/input/Input.h | 9 ++ include/input/InputEventBuilders.h | 17 +- libs/input/Input.cpp | 100 ++++++++++++ libs/input/tests/InputEvent_test.cpp | 152 ++++++++++++++++++ .../dispatcher/InputDispatcher.cpp | 73 +-------- 5 files changed, 282 insertions(+), 69 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index a84dcfc63c..374254fc84 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -870,6 +870,10 @@ public: void copyFrom(const MotionEvent* other, bool keepHistory); + // Initialize this event by keeping only the pointers from "other" that are in splitPointerIds. + void splitFrom(const MotionEvent& other, std::bitset splitPointerIds, + int32_t newEventId); + void addSample( nsecs_t eventTime, const PointerCoords* pointerCoords); @@ -910,6 +914,11 @@ public: static std::string actionToString(int32_t action); + static std::tuple, + std::vector> + split(int32_t action, int32_t flags, int32_t historySize, const std::vector&, + const std::vector&, std::bitset splitPointerIds); + // MotionEvent will transform various axes in different ways, based on the source. For // example, the x and y axes will not have any offsets/translations applied if it comes from a // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h index 2d23b97386..c0c5e2412d 100644 --- a/include/input/InputEventBuilders.h +++ b/include/input/InputEventBuilders.h @@ -118,6 +118,16 @@ public: return *this; } + MotionEventBuilder& transform(ui::Transform t) { + mTransform = t; + return *this; + } + + MotionEventBuilder& rawTransform(ui::Transform t) { + mRawTransform = t; + return *this; + } + MotionEvent build() { std::vector pointerProperties; std::vector pointerCoords; @@ -134,12 +144,11 @@ public: } MotionEvent event; - static const ui::Transform kIdentityTransform; event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC, mAction, mActionButton, mFlags, /*edgeFlags=*/0, AMETA_NONE, mButtonState, - MotionClassification::NONE, kIdentityTransform, + MotionClassification::NONE, mTransform, /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition, - mRawYCursorPosition, kIdentityTransform, mDownTime, mEventTime, + mRawYCursorPosition, mRawTransform, mDownTime, mEventTime, mPointers.size(), pointerProperties.data(), pointerCoords.data()); return event; } @@ -156,6 +165,8 @@ private: int32_t mFlags{0}; float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + ui::Transform mTransform; + ui::Transform mRawTransform; std::vector mPointers; }; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 9e0ce1db33..d58fb42f05 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -60,6 +60,45 @@ bool shouldDisregardOffset(uint32_t source) { return !isFromSource(source, AINPUT_SOURCE_CLASS_POINTER); } +int32_t resolveActionForSplitMotionEvent( + int32_t action, int32_t flags, const std::vector& pointerProperties, + const std::vector& splitPointerProperties) { + LOG_ALWAYS_FATAL_IF(splitPointerProperties.empty()); + const auto maskedAction = MotionEvent::getActionMasked(action); + if (maskedAction != AMOTION_EVENT_ACTION_POINTER_DOWN && + maskedAction != AMOTION_EVENT_ACTION_POINTER_UP) { + // The action is unaffected by splitting this motion event. + return action; + } + const auto actionIndex = MotionEvent::getActionIndex(action); + if (CC_UNLIKELY(actionIndex >= pointerProperties.size())) { + LOG(FATAL) << "Action index is out of bounds, index: " << actionIndex; + } + + const auto affectedPointerId = pointerProperties[actionIndex].id; + std::optional splitActionIndex; + for (uint32_t i = 0; i < splitPointerProperties.size(); i++) { + if (affectedPointerId == splitPointerProperties[i].id) { + splitActionIndex = i; + break; + } + } + if (!splitActionIndex.has_value()) { + // The affected pointer is not part of the split motion event. + return AMOTION_EVENT_ACTION_MOVE; + } + + if (splitPointerProperties.size() > 1) { + return maskedAction | (*splitActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + return ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) ? AMOTION_EVENT_ACTION_CANCEL + : AMOTION_EVENT_ACTION_UP; + } + return AMOTION_EVENT_ACTION_DOWN; +} + } // namespace const char* motionClassificationToString(MotionClassification classification) { @@ -584,6 +623,28 @@ void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) { } } +void MotionEvent::splitFrom(const android::MotionEvent& other, + std::bitset splitPointerIds, int32_t newEventId) { + // TODO(b/327503168): The down time should be a parameter to the split function, because only + // the caller can know when the first event went down on the target. + const nsecs_t splitDownTime = other.mDownTime; + + auto [action, pointerProperties, pointerCoords] = + split(other.getAction(), other.getFlags(), other.getHistorySize(), + other.mPointerProperties, other.mSamplePointerCoords, splitPointerIds); + + // Initialize the event with zero pointers, and manually set the split pointers. + initialize(newEventId, other.mDeviceId, other.mSource, other.mDisplayId, /*hmac=*/{}, action, + other.mActionButton, other.mFlags, other.mEdgeFlags, other.mMetaState, + other.mButtonState, other.mClassification, other.mTransform, other.mXPrecision, + other.mYPrecision, other.mRawXCursorPosition, other.mRawYCursorPosition, + other.mRawTransform, splitDownTime, other.getEventTime(), /*pointerCount=*/0, + pointerProperties.data(), pointerCoords.data()); + mPointerProperties = std::move(pointerProperties); + mSamplePointerCoords = std::move(pointerCoords); + mSampleEventTimes = other.mSampleEventTimes; +} + void MotionEvent::addSample( int64_t eventTime, const PointerCoords* pointerCoords) { @@ -934,6 +995,45 @@ std::string MotionEvent::actionToString(int32_t action) { return android::base::StringPrintf("%" PRId32, action); } +std::tuple, std::vector> MotionEvent::split( + int32_t action, int32_t flags, int32_t historySize, + const std::vector& pointerProperties, + const std::vector& pointerCoords, + std::bitset splitPointerIds) { + LOG_ALWAYS_FATAL_IF(!splitPointerIds.any()); + const auto pointerCount = pointerProperties.size(); + LOG_ALWAYS_FATAL_IF(pointerCoords.size() != (pointerCount * (historySize + 1))); + const auto splitCount = splitPointerIds.count(); + + std::vector splitPointerProperties; + std::vector splitPointerCoords; + + for (uint32_t i = 0; i < pointerCount; i++) { + if (splitPointerIds.test(pointerProperties[i].id)) { + splitPointerProperties.emplace_back(pointerProperties[i]); + } + } + for (uint32_t i = 0; i < pointerCoords.size(); i++) { + if (splitPointerIds.test(pointerProperties[i % pointerCount].id)) { + splitPointerCoords.emplace_back(pointerCoords[i]); + } + } + LOG_ALWAYS_FATAL_IF(splitPointerCoords.size() != + (splitPointerProperties.size() * (historySize + 1))); + + if (CC_UNLIKELY(splitPointerProperties.size() != splitCount)) { + LOG(FATAL) << "Cannot split MotionEvent: Requested splitting " << splitCount + << " pointers from the original event, but the original event only contained " + << splitPointerProperties.size() << " of those pointers."; + } + + // TODO(b/327503168): Verify the splitDownTime here once it is used correctly. + + const auto splitAction = resolveActionForSplitMotionEvent(action, flags, pointerProperties, + splitPointerProperties); + return {splitAction, splitPointerProperties, splitPointerCoords}; +} + // Apply the given transformation to the point without checking whether the entire transform // should be disregarded altogether for the provided source. static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform, diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index a9655730fc..540766d66c 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace android { @@ -31,6 +32,18 @@ static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; +static constexpr auto POINTER_0_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + +static constexpr auto POINTER_1_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + +static constexpr auto POINTER_0_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + +static constexpr auto POINTER_1_UP = + AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + class BaseTest : public testing::Test { protected: static constexpr std::array HMAC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, @@ -554,6 +567,145 @@ TEST_F(MotionEventTest, CopyFrom_DoNotKeepHistory) { ASSERT_EQ(event.getX(0), copy.getX(0)); } +TEST_F(MotionEventTest, SplitPointerDown) { + MotionEvent event = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4)) + .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6)) + .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8)) + .build(); + + MotionEvent splitDown; + std::bitset splitDownIds{}; + splitDownIds.set(6, true); + splitDown.splitFrom(event, splitDownIds, /*eventId=*/42); + ASSERT_EQ(splitDown.getAction(), AMOTION_EVENT_ACTION_DOWN); + ASSERT_EQ(splitDown.getPointerCount(), 1u); + ASSERT_EQ(splitDown.getPointerId(0), 6); + ASSERT_EQ(splitDown.getX(0), 6); + ASSERT_EQ(splitDown.getY(0), 6); + + MotionEvent splitPointerDown; + std::bitset splitPointerDownIds{}; + splitPointerDownIds.set(6, true); + splitPointerDownIds.set(8, true); + splitPointerDown.splitFrom(event, splitPointerDownIds, /*eventId=*/42); + ASSERT_EQ(splitPointerDown.getAction(), POINTER_0_DOWN); + ASSERT_EQ(splitPointerDown.getPointerCount(), 2u); + ASSERT_EQ(splitPointerDown.getPointerId(0), 6); + ASSERT_EQ(splitPointerDown.getX(0), 6); + ASSERT_EQ(splitPointerDown.getY(0), 6); + ASSERT_EQ(splitPointerDown.getPointerId(1), 8); + ASSERT_EQ(splitPointerDown.getX(1), 8); + ASSERT_EQ(splitPointerDown.getY(1), 8); + + MotionEvent splitMove; + std::bitset splitMoveIds{}; + splitMoveIds.set(4, true); + splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43); + ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE); + ASSERT_EQ(splitMove.getPointerCount(), 1u); + ASSERT_EQ(splitMove.getPointerId(0), 4); + ASSERT_EQ(splitMove.getX(0), 4); + ASSERT_EQ(splitMove.getY(0), 4); +} + +TEST_F(MotionEventTest, SplitPointerUp) { + MotionEvent event = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4)) + .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6)) + .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8)) + .build(); + + MotionEvent splitUp; + std::bitset splitUpIds{}; + splitUpIds.set(4, true); + splitUp.splitFrom(event, splitUpIds, /*eventId=*/42); + ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_UP); + ASSERT_EQ(splitUp.getPointerCount(), 1u); + ASSERT_EQ(splitUp.getPointerId(0), 4); + ASSERT_EQ(splitUp.getX(0), 4); + ASSERT_EQ(splitUp.getY(0), 4); + + MotionEvent splitPointerUp; + std::bitset splitPointerUpIds{}; + splitPointerUpIds.set(4, true); + splitPointerUpIds.set(8, true); + splitPointerUp.splitFrom(event, splitPointerUpIds, /*eventId=*/42); + ASSERT_EQ(splitPointerUp.getAction(), POINTER_0_UP); + ASSERT_EQ(splitPointerUp.getPointerCount(), 2u); + ASSERT_EQ(splitPointerUp.getPointerId(0), 4); + ASSERT_EQ(splitPointerUp.getX(0), 4); + ASSERT_EQ(splitPointerUp.getY(0), 4); + ASSERT_EQ(splitPointerUp.getPointerId(1), 8); + ASSERT_EQ(splitPointerUp.getX(1), 8); + ASSERT_EQ(splitPointerUp.getY(1), 8); + + MotionEvent splitMove; + std::bitset splitMoveIds{}; + splitMoveIds.set(6, true); + splitMoveIds.set(8, true); + splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43); + ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE); + ASSERT_EQ(splitMove.getPointerCount(), 2u); + ASSERT_EQ(splitMove.getPointerId(0), 6); + ASSERT_EQ(splitMove.getX(0), 6); + ASSERT_EQ(splitMove.getY(0), 6); + ASSERT_EQ(splitMove.getPointerId(1), 8); + ASSERT_EQ(splitMove.getX(1), 8); + ASSERT_EQ(splitMove.getY(1), 8); +} + +TEST_F(MotionEventTest, SplitPointerUpCancel) { + MotionEvent event = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4)) + .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6)) + .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8)) + .addFlag(AMOTION_EVENT_FLAG_CANCELED) + .build(); + + MotionEvent splitUp; + std::bitset splitUpIds{}; + splitUpIds.set(6, true); + splitUp.splitFrom(event, splitUpIds, /*eventId=*/42); + ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_CANCEL); + ASSERT_EQ(splitUp.getPointerCount(), 1u); + ASSERT_EQ(splitUp.getPointerId(0), 6); + ASSERT_EQ(splitUp.getX(0), 6); + ASSERT_EQ(splitUp.getY(0), 6); +} + +TEST_F(MotionEventTest, SplitPointerMove) { + MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4)) + .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6)) + .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8)) + .transform(ui::Transform(ui::Transform::ROT_90, 100, 100)) + .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50)) + .build(); + + MotionEvent splitMove; + std::bitset splitMoveIds{}; + splitMoveIds.set(4, true); + splitMoveIds.set(8, true); + splitMove.splitFrom(event, splitMoveIds, /*eventId=*/42); + ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE); + ASSERT_EQ(splitMove.getPointerCount(), 2u); + ASSERT_EQ(splitMove.getPointerId(0), 4); + ASSERT_EQ(splitMove.getX(0), event.getX(0)); + ASSERT_EQ(splitMove.getY(0), event.getY(0)); + ASSERT_EQ(splitMove.getRawX(0), event.getRawX(0)); + ASSERT_EQ(splitMove.getRawY(0), event.getRawY(0)); + ASSERT_EQ(splitMove.getPointerId(1), 8); + ASSERT_EQ(splitMove.getX(1), event.getX(2)); + ASSERT_EQ(splitMove.getY(1), event.getY(2)); + ASSERT_EQ(splitMove.getRawX(1), event.getRawX(2)); + ASSERT_EQ(splitMove.getRawY(1), event.getRawY(2)); +} + TEST_F(MotionEventTest, OffsetLocation) { MotionEvent event; initializeEventWithHistory(&event); diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8858f0caec..bedb681366 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4268,72 +4268,13 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( std::unique_ptr InputDispatcher::splitMotionEvent( const MotionEntry& originalMotionEntry, std::bitset pointerIds, nsecs_t splitDownTime) { - ALOG_ASSERT(pointerIds.any()); - - uint32_t splitPointerIndexMap[MAX_POINTERS]; - std::vector splitPointerProperties; - std::vector splitPointerCoords; - - uint32_t originalPointerCount = originalMotionEntry.getPointerCount(); - uint32_t splitPointerCount = 0; - - for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount; - originalPointerIndex++) { - const PointerProperties& pointerProperties = - originalMotionEntry.pointerProperties[originalPointerIndex]; - uint32_t pointerId = uint32_t(pointerProperties.id); - if (pointerIds.test(pointerId)) { - splitPointerIndexMap[splitPointerCount] = originalPointerIndex; - splitPointerProperties.push_back(pointerProperties); - splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]); - splitPointerCount += 1; - } - } - - if (splitPointerCount != pointerIds.count()) { - // This is bad. We are missing some of the pointers that we expected to deliver. - // Most likely this indicates that we received an ACTION_MOVE events that has - // different pointer ids than we expected based on the previous ACTION_DOWN - // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers - // in this way. - ALOGW("Dropping split motion event because the pointer count is %d but " - "we expected there to be %zu pointers. This probably means we received " - "a broken sequence of pointer ids from the input device: %s", - splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str()); - return nullptr; - } - - int32_t action = originalMotionEntry.action; - int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK; - if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN || - maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { - int32_t originalPointerIndex = MotionEvent::getActionIndex(action); - const PointerProperties& pointerProperties = - originalMotionEntry.pointerProperties[originalPointerIndex]; - uint32_t pointerId = uint32_t(pointerProperties.id); - if (pointerIds.test(pointerId)) { - if (pointerIds.count() == 1) { - // The first/last pointer went down/up. - action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN - ? AMOTION_EVENT_ACTION_DOWN - : (originalMotionEntry.flags & AMOTION_EVENT_FLAG_CANCELED) != 0 - ? AMOTION_EVENT_ACTION_CANCEL - : AMOTION_EVENT_ACTION_UP; - } else { - // A secondary pointer went down/up. - uint32_t splitPointerIndex = 0; - while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) { - splitPointerIndex += 1; - } - action = maskedAction | - (splitPointerIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - } - } else { - // An unrelated pointer changed. - action = AMOTION_EVENT_ACTION_MOVE; - } - } + const auto& [action, pointerProperties, pointerCoords] = + MotionEvent::split(originalMotionEntry.action, originalMotionEntry.flags, + /*historySize=*/0, originalMotionEntry.pointerProperties, + originalMotionEntry.pointerCoords, pointerIds); + // TODO(b/327503168): Move this check inside MotionEvent::split once all callers handle it + // correctly. if (action == AMOTION_EVENT_ACTION_DOWN && splitDownTime != originalMotionEntry.eventTime) { logDispatchStateLocked(); LOG_ALWAYS_FATAL("Split motion event has mismatching downTime and eventTime for " @@ -4361,7 +4302,7 @@ std::unique_ptr InputDispatcher::splitMotionEvent( originalMotionEntry.yPrecision, originalMotionEntry.xCursorPosition, originalMotionEntry.yCursorPosition, splitDownTime, - splitPointerProperties, splitPointerCoords); + pointerProperties, pointerCoords); return splitMotionEntry; } -- GitLab From 52f0fc4df9998505718c1f89dba99aa6893d6709 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Tue, 5 Mar 2024 07:53:06 +0000 Subject: [PATCH 012/465] binder_parcel_fuzz: add setData This causes crashes, due to mObjects not getting cleared or similar? This is not an operation that should be done, but we should add tests for these cases and fix them. Bug: 328161314 Test: run fuzzer (crashes quickly) Change-Id: Ib737e81fbf53a2e1223cbdcde6ed50d1b6f02b24 --- libs/binder/tests/parcel_fuzzer/binder.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp index 08fe071bfb..d9f1d758b3 100644 --- a/libs/binder/tests/parcel_fuzzer/binder.cpp +++ b/libs/binder/tests/parcel_fuzzer/binder.cpp @@ -115,6 +115,14 @@ std::vector> BINDER_PARCEL_READ_FUNCTIONS { p.setDataPosition(pos); FUZZ_LOG() << "setDataPosition done"; }, + [] (const ::android::Parcel& p, FuzzedDataProvider& provider) { + size_t len = provider.ConsumeIntegralInRange(0, 1024); + std::vector bytes = provider.ConsumeBytes(len); + FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null"); + // TODO: allow all read and write operations + (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size()); + FUZZ_LOG() << "setData done"; + }, PARCEL_READ_NO_STATUS(size_t, allowFds), PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors), PARCEL_READ_NO_STATUS(std::vector>, debugReadAllStrongBinders), -- GitLab From c6b0dfa3358a152c9bfd1fc479753c6ba32b78eb Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Tue, 5 Mar 2024 09:10:02 +0000 Subject: [PATCH 013/465] Parcel: free objects before realloc Otherwise this would try to free the objects which have been written over in mData. Bug: 328177618 Test: with fuzzer Change-Id: I8929d11e3c1c193a1c36e95371b5e96e24d47ece --- libs/binder/Parcel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index c1770b35d1..4d1463ca09 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -2930,14 +2930,14 @@ status_t Parcel::restartWrite(size_t desired) return continueWrite(desired); } + releaseObjects(); + uint8_t* data = reallocZeroFree(mData, mDataCapacity, desired, mDeallocZero); if (!data && desired > mDataCapacity) { mError = NO_MEMORY; return NO_MEMORY; } - releaseObjects(); - if (data || desired == 0) { LOG_ALLOC("Parcel %p: restart from %zu to %zu capacity", this, mDataCapacity, desired); if (mDataCapacity > desired) { -- GitLab From c14cebc9b2f0e36cbca3569dfadb59e836bc7f52 Mon Sep 17 00:00:00 2001 From: Kean Mariotti Date: Mon, 4 Mar 2024 10:55:28 +0000 Subject: [PATCH 014/465] Ignore "use pre-dump" flag if no data is available In some cases dumpstate might be requested to create a bugreport with "use pre-dump" flag, even if no pre-dumped data is available on disk. In such cases dumpstate should ignore the "use pre-dump" flag. Example scenario where the pre-dumped data would not be available: 1. Pre-dump data 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK) 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK) Bug: 327322141 Test: atest com.android.os.bugreports.tests.BugreportManagerTest Change-Id: Ice75f18437064daad98857c8de8a07c7e2994e0c Ignore-AOSP-First: depends on changes not available in AOSP (e.g. I343813929a537c601132dd15db5e2c4d3fbbdcb1) --- cmds/dumpstate/dumpstate.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 8d37aac0cb..826a8dbc2c 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -3280,6 +3280,12 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, // duration is logged into MYLOG instead. PrintHeader(); + bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0; + if (options_->use_predumped_ui_data && !system_trace_exists) { + MYLOGW("Ignoring 'use predumped data' flag because no predumped data is available"); + options_->use_predumped_ui_data = false; + } + std::future snapshot_system_trace; bool is_dumpstate_restricted = -- GitLab From 5e5645e860048bcdfef5db01b936e73357174caf Mon Sep 17 00:00:00 2001 From: Linnan Li Date: Tue, 5 Mar 2024 14:43:05 +0000 Subject: [PATCH 015/465] Move occlusion detection into logical space Same as our "hit test" to find the target window, since WM works in logical space, we consider the window's frame to be (l, t, r, b), and the portion of the window that can be occluded in the x-direction is [l, r), and the portion of the window that can be occluded in the y-direction is [t, b). When the logical space is different from the physical space, the opening and closing intervals in each of these directions will be inconsistent, leading to abnormal detection of the edge part. Here we move the occlusion detection to the logical space as well, consistent with the "hit test" for finding the target window. Bug: 327712879 Test: atest inputflinger_tests Test: Create a window that can cause occlusion and perform clicks on the edges, making sure that the occlusion range is x = [l, r), y = [t, b) under each direction of the screen. Signed-off-by: Linnan Li (cherry picked from https://partner-android-review.googlesource.com/q/commit:1a0060e9bcd25bee6ce7a9f5d01b9446b0c4d73b) Merged-In: I19b739bc8f1e48d8bc70020a2b07da227eaa6d8b Change-Id: I19b739bc8f1e48d8bc70020a2b07da227eaa6d8b --- libs/gui/WindowInfo.cpp | 8 -- libs/gui/include/gui/WindowInfo.h | 4 - .../dispatcher/InputDispatcher.cpp | 21 ++++- .../inputflinger/dispatcher/InputDispatcher.h | 4 +- .../tests/InputDispatcher_test.cpp | 88 +++++++++++++++++++ 5 files changed, 107 insertions(+), 18 deletions(-) diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp index 86bf0ee745..ad0d99d11e 100644 --- a/libs/gui/WindowInfo.cpp +++ b/libs/gui/WindowInfo.cpp @@ -73,14 +73,6 @@ void WindowInfo::addTouchableRegion(const Rect& region) { touchableRegion.orSelf(region); } -bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const { - return touchableRegion.contains(x, y); -} - -bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const { - return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom; -} - bool WindowInfo::supportsSplitTouch() const { return !inputConfig.test(InputConfig::PREVENT_SPLITTING); } diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index 32d60be612..2d1b51a418 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -254,10 +254,6 @@ struct WindowInfo : public Parcelable { void addTouchableRegion(const Rect& region); - bool touchableRegionContainsPoint(int32_t x, int32_t y) const; - - bool frameContainsPoint(int32_t x, int32_t y) const; - bool supportsSplitTouch() const; bool isSpy() const; diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 7f54bf1945..55976137c5 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -573,6 +573,18 @@ bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float return true; } +// Returns true if the given window's frame can occlude pointer events at the given display +// location. +bool windowOccludesTouchAt(const WindowInfo& windowInfo, int displayId, float x, float y, + const ui::Transform& displayTransform) { + if (windowInfo.displayId != displayId) { + return false; + } + const auto frame = displayTransform.transform(windowInfo.frame); + const auto p = floor(displayTransform.transform(x, y)); + return p.x >= frame.left && p.x < frame.right && p.y >= frame.top && p.y < frame.bottom; +} + bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) { return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(entry.pointerProperties[pointerIndex].toolType); @@ -3056,7 +3068,7 @@ static bool canBeObscuredBy(const sp& windowHandle, * If neither of those is true, then it means the touch can be allowed. */ InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked( - const sp& windowHandle, int32_t x, int32_t y) const { + const sp& windowHandle, float x, float y) const { const WindowInfo* windowInfo = windowHandle->getInfo(); int32_t displayId = windowInfo->displayId; const std::vector>& windowHandles = getWindowHandlesLocked(displayId); @@ -3070,7 +3082,8 @@ InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLo break; // All future windows are below us. Exit early. } const WindowInfo* otherInfo = otherHandle->getInfo(); - if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->frameContainsPoint(x, y) && + if (canBeObscuredBy(windowHandle, otherHandle) && + windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId)) && !haveSameApplicationToken(windowInfo, otherInfo)) { if (DEBUG_TOUCH_OCCLUSION) { info.debugInfo.push_back( @@ -3140,7 +3153,7 @@ bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionIn } bool InputDispatcher::isWindowObscuredAtPointLocked(const sp& windowHandle, - int32_t x, int32_t y) const { + float x, float y) const { int32_t displayId = windowHandle->getInfo()->displayId; const std::vector>& windowHandles = getWindowHandlesLocked(displayId); for (const sp& otherHandle : windowHandles) { @@ -3149,7 +3162,7 @@ bool InputDispatcher::isWindowObscuredAtPointLocked(const sp& } const WindowInfo* otherInfo = otherHandle->getInfo(); if (canBeObscuredBy(windowHandle, otherHandle) && - otherInfo->frameContainsPoint(x, y)) { + windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) { return true; } } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 269bfddb8c..9c8d5889d1 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -565,11 +565,11 @@ private: }; TouchOcclusionInfo computeTouchOcclusionInfoLocked( - const sp& windowHandle, int32_t x, int32_t y) const + const sp& windowHandle, float x, float y) const REQUIRES(mLock); bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock); bool isWindowObscuredAtPointLocked(const sp& windowHandle, - int32_t x, int32_t y) const REQUIRES(mLock); + float x, float y) const REQUIRES(mLock); bool isWindowObscuredLocked(const sp& windowHandle) const REQUIRES(mLock); std::string dumpWindowForTouchOcclusion(const android::gui::WindowInfo* info, diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index f0f4d93ecd..621d9eadb8 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -5361,6 +5361,94 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) window->assertNoEvents(); } +// This test verifies the occlusion detection for all rotations of the display by tapping +// in different locations on the display, specifically points close to the four corners of a +// window. +TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOrientations) { + constexpr static int32_t displayWidth = 400; + constexpr static int32_t displayHeight = 800; + + std::shared_ptr untrustedWindowApplication = + std::make_shared(); + std::shared_ptr application = std::make_shared(); + + const auto rotation = GetParam(); + + // Set up the display with the specified rotation. + const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270; + const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth; + const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight; + const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation), + logicalDisplayWidth, logicalDisplayHeight); + addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform); + + // Create a window that not trusted. + const Rect untrustedWindowFrameInLogicalDisplay(100, 100, 200, 300); + + const Rect untrustedWindowFrameInDisplay = + displayTransform.inverse().transform(untrustedWindowFrameInLogicalDisplay); + + sp untrustedWindow = + sp::make(untrustedWindowApplication, mDispatcher, "UntrustedWindow", + ADISPLAY_ID_DEFAULT); + untrustedWindow->setFrame(untrustedWindowFrameInDisplay, displayTransform); + untrustedWindow->setTrustedOverlay(false); + untrustedWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); + untrustedWindow->setTouchable(false); + untrustedWindow->setAlpha(1.0f); + untrustedWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101}); + addWindow(untrustedWindow); + + // Create a simple app window below the untrusted window. + const Rect simpleAppWindowFrameInLogicalDisplay(0, 0, 300, 600); + const Rect simpleAppWindowFrameInDisplay = + displayTransform.inverse().transform(simpleAppWindowFrameInLogicalDisplay); + + sp simpleAppWindow = + sp::make(application, mDispatcher, "SimpleAppWindow", + ADISPLAY_ID_DEFAULT); + simpleAppWindow->setFrame(simpleAppWindowFrameInDisplay, displayTransform); + simpleAppWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202}); + addWindow(simpleAppWindow); + + // The following points in logical display space should be inside the untrusted window, so + // the simple window could not receive events that coordinate is these point. + static const std::array untrustedPoints{ + {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}}; + + for (const auto untrustedPoint : untrustedPoints) { + const vec2 p = displayTransform.inverse().transform(untrustedPoint); + const PointF pointInDisplaySpace{p.x, p.y}; + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); + } + untrustedWindow->assertNoEvents(); + simpleAppWindow->assertNoEvents(); + // The following points in logical display space should be outside the untrusted window, so + // the simple window should receive events that coordinate is these point. + static const std::array trustedPoints{ + {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}}; + for (const auto trustedPoint : trustedPoints) { + const vec2 p = displayTransform.inverse().transform(trustedPoint); + const PointF pointInDisplaySpace{p.x, p.y}; + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); + simpleAppWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); + mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, + AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + {pointInDisplaySpace})); + simpleAppWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, + AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); + } + untrustedWindow->assertNoEvents(); +} + // Run the precision tests for all rotations. INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests, InputDispatcherDisplayOrientationFixture, -- GitLab From 41859c4b5d51e2d7d9405200e3f06a6fd1072d0e Mon Sep 17 00:00:00 2001 From: Linnan Li Date: Tue, 5 Mar 2024 16:20:34 +0000 Subject: [PATCH 016/465] Create hover input target by addPointerWindowTargetLocked The hover event is also pointer event, we should use addPointerWindowTargetLocked to create input target, this will improve code maintainability. Bug: 328151597 Test: atest inputflinger_tests Signed-off-by: Linnan Li (cherry picked from https://partner-android-review.googlesource.com/q/commit:f05a78743cc97f164e867790465f47ed830d46fb) Merged-In: Ie23d9e4fa2aa79f692624ab286c113d6cdac5b67 Change-Id: Ie23d9e4fa2aa79f692624ab286c113d6cdac5b67 --- .../dispatcher/InputDispatcher.cpp | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 7f54bf1945..8e75c42c3c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -656,13 +656,13 @@ std::optional getDownTime(const EventEntry& eventEntry) { std::vector getHoveringWindowsLocked(const TouchState* oldState, const TouchState& newTouchState, const MotionEntry& entry) { - std::vector out; const int32_t maskedAction = MotionEvent::getActionMasked(entry.action); if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) { // ACTION_SCROLL events should not affect the hovering pointer dispatch return {}; } + std::vector out; // We should consider all hovering pointers here. But for now, just use the first one const PointerProperties& pointer = entry.pointerProperties[0]; @@ -2633,19 +2633,14 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( { std::vector hoveringWindows = getHoveringWindowsLocked(oldState, tempTouchState, entry); + // Hardcode to single hovering pointer for now. + std::bitset pointerIds; + pointerIds.set(entry.pointerProperties[0].id); for (const TouchedWindow& touchedWindow : hoveringWindows) { - std::optional target = - createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode, - touchedWindow.targetFlags, - touchedWindow.getDownTimeInTarget(entry.deviceId)); - if (!target) { - continue; - } - // Hardcode to single hovering pointer for now. - std::bitset pointerIds; - pointerIds.set(entry.pointerProperties[0].id); - target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform); - targets.push_back(*target); + addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode, + touchedWindow.targetFlags, pointerIds, + touchedWindow.getDownTimeInTarget(entry.deviceId), + targets); } } -- GitLab From 8eb00cc0a03413e68e71c4938b74ff4dd8225da9 Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Tue, 5 Mar 2024 16:21:43 +0000 Subject: [PATCH 017/465] [contextualsearch] wire new feature into build Bug: 326143814 Test: wcgw Change-Id: Ic1404e771cf071cde1152df79465575fccd2912e --- data/etc/Android.bp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 8b2842a3ae..eedf1410a1 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -352,6 +352,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.software.contextualsearch.prebuilt.xml", + src: "android.software.contextualsearch.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.software.device_id_attestation.prebuilt.xml", src: "android.software.device_id_attestation.xml", -- GitLab From 82d524e49b4f0acd9ad1a85599500df84fcfc0d2 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Fri, 23 Feb 2024 02:37:38 +0000 Subject: [PATCH 018/465] Reland refactor of screenshot code on main thread. Create helper functions to improve readability of what is scheduled on the SurfaceFlinger main thread. This will allow for cleaner changes in reducing the calls on the main thread for screenshots. Changes include some renaming for better clarity. Bug: b/294936197 Test: presubmit Test: atest SurfaceFlinger_test Change-Id: I3643b27b98e20578c51f90f6ab61d1aa2e3458bb --- .../surfaceflinger/RegionSamplingThread.cpp | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 103 +++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 6 +- 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index c888ccc8ae..77e045d6f9 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -377,8 +377,8 @@ void RegionSamplingThread::captureSample() { constexpr bool kIsProtected = false; if (const auto fenceResult = - mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer, - kRegionSampling, kGrayscale, kIsProtected, nullptr) + mFlinger.captureScreenshot(std::move(renderAreaFuture), getLayerSnapshots, buffer, + kRegionSampling, kGrayscale, kIsProtected, nullptr) .get(); fenceResult.ok()) { fenceResult.value()->waitForever(LOG_TAG); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cf5f55d7bd..8847ceea44 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8063,6 +8063,19 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, args.allowProtected, args.grayscale, captureListener); } +bool SurfaceFlinger::layersHasProtectedLayer( + const std::vector>>& layers) const { + bool protectedLayerFound = false; + for (auto& [_, layerFe] : layers) { + protectedLayerFound |= + (layerFe->mSnapshot->isVisible && layerFe->mSnapshot->hasProtectedContent); + if (protectedLayerFound) { + break; + } + } + return protectedLayerFound; +} + void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, ui::Size bufferSize, ui::PixelFormat reqPixelFormat, @@ -8085,18 +8098,9 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture, const bool supportsProtected = getRenderEngine().supportsProtectedContent(); bool hasProtectedLayer = false; if (allowProtected && supportsProtected) { - hasProtectedLayer = mScheduler - ->schedule([=]() { - bool protectedLayerFound = false; - auto layers = getLayerSnapshots(); - for (auto& [_, layerFe] : layers) { - protectedLayerFound |= - (layerFe->mSnapshot->isVisible && - layerFe->mSnapshot->hasProtectedContent); - } - return protectedLayerFound; - }) - .get(); + // Snapshots must be taken from the main thread. + auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get(); + hasProtectedLayer = layersHasProtectedLayer(layers); } const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected; const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | @@ -8121,52 +8125,53 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture, renderengine::impl::ExternalTexture>(buffer, getRenderEngine(), renderengine::impl::ExternalTexture::Usage:: WRITEABLE); - auto fence = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture, - false /* regionSampling */, grayscale, isProtected, - captureListener); - fence.get(); + auto futureFence = + captureScreenshot(std::move(renderAreaFuture), getLayerSnapshots, texture, + false /* regionSampling */, grayscale, isProtected, captureListener); + futureFence.get(); } -ftl::SharedFuture SurfaceFlinger::captureScreenCommon( +ftl::SharedFuture SurfaceFlinger::captureScreenshot( RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, const sp& captureListener) { ATRACE_CALL(); - auto future = mScheduler->schedule( - [=, this, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD( - kMainThreadContext) mutable -> ftl::SharedFuture { - ScreenCaptureResults captureResults; - std::shared_ptr renderArea = renderAreaFuture.get(); - if (!renderArea) { - ALOGW("Skipping screen capture because of invalid render area."); - if (captureListener) { - captureResults.fenceResult = base::unexpected(NO_MEMORY); - captureListener->onScreenCaptureCompleted(captureResults); - } - return ftl::yield(base::unexpected(NO_ERROR)).share(); - } + auto takeScreenshotFn = [=, this, renderAreaFuture = std::move(renderAreaFuture)]() REQUIRES( + kMainThreadContext) mutable -> ftl::SharedFuture { + ScreenCaptureResults captureResults; + std::shared_ptr renderArea = renderAreaFuture.get(); + if (!renderArea) { + ALOGW("Skipping screen capture because of invalid render area."); + if (captureListener) { + captureResults.fenceResult = base::unexpected(NO_MEMORY); + captureListener->onScreenCaptureCompleted(captureResults); + } + return ftl::yield(base::unexpected(NO_ERROR)).share(); + } - ftl::SharedFuture renderFuture; - renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) { - renderFuture = - renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling, - grayscale, isProtected, captureResults); - }); + ftl::SharedFuture renderFuture; + renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) { + renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling, + grayscale, isProtected, captureResults); + }); - if (captureListener) { - // Defer blocking on renderFuture back to the Binder thread. - return ftl::Future(std::move(renderFuture)) - .then([captureListener, captureResults = std::move(captureResults)]( - FenceResult fenceResult) mutable -> FenceResult { - captureResults.fenceResult = std::move(fenceResult); - captureListener->onScreenCaptureCompleted(captureResults); - return base::unexpected(NO_ERROR); - }) - .share(); - } - return renderFuture; - }); + if (captureListener) { + // Defer blocking on renderFuture back to the Binder thread. + return ftl::Future(std::move(renderFuture)) + .then([captureListener, captureResults = std::move(captureResults)]( + FenceResult fenceResult) mutable -> FenceResult { + captureResults.fenceResult = std::move(fenceResult); + captureListener->onScreenCaptureCompleted(captureResults); + return base::unexpected(NO_ERROR); + }) + .share(); + } + return renderFuture; + }; + + auto future = + mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn))); // Flatten nested futures. auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture future) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 678be54c41..1ce8606103 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -874,13 +874,17 @@ private: // Boot animation, on/off animations and screen capture void startBootAnim(); + bool layersHasProtectedLayer(const std::vector>>& layers) const; + void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize, ui::PixelFormat, bool allowProtected, bool grayscale, const sp&); - ftl::SharedFuture captureScreenCommon( + + ftl::SharedFuture captureScreenshot( RenderAreaFuture, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, const sp&); + ftl::SharedFuture renderScreenImpl( std::shared_ptr, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, -- GitLab From add8a4a9c3abd2c2b8d75b41bb99b7fb607e549a Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 5 Mar 2024 22:18:09 +0000 Subject: [PATCH 019/465] MotionEvent: Get offsets in raw coordinate space MotionEvents store the underlying axis values in the coordinate space of the physical display, or "untransformed" space. The MotionEvent's mRawTransform takes those coordinates into the coordinate space of the logical display, or "raw" coordinates. The MotionEvent's mTransform takes those coordinates into the window/View's local coordinates. Previously, getting the motion event offset would return the offset with respect to the origin of the "untransformed" space. This is of little value to callers, since they are expecting the offset in "raw" coordinates, which is the offset with respect to the logical display's origin. To calculate the raw offset, we calculate where the raw point (0, 0) would map to in untransformed coordinates by applying the inverse raw transform, and then apply the window transform. Bug: 249340921 Test: atest libinput_tests Test: atest inputflinger_tests Change-Id: Iadbdde4dd45b5527b73be863b198b4c9a9e713cc --- include/input/Input.h | 20 +++++++++++++++---- libs/input/Input.cpp | 12 +++++++++++ libs/input/tests/InputEvent_test.cpp | 18 +++++++++++------ .../tests/InputPublisherAndConsumer_test.cpp | 6 ++++-- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index a84dcfc63c..027e4a5bdc 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -662,10 +662,6 @@ public: inline void setActionButton(int32_t button) { mActionButton = button; } - inline float getXOffset() const { return mTransform.tx(); } - - inline float getYOffset() const { return mTransform.ty(); } - inline const ui::Transform& getTransform() const { return mTransform; } std::optional getSurfaceRotation() const; @@ -876,6 +872,22 @@ public: void offsetLocation(float xOffset, float yOffset); + /** + * Get the X offset of this motion event relative to the origin of the raw coordinate space. + * + * In practice, this is the delta that was added to the raw screen coordinates (i.e. in logical + * display space) to adjust for the absolute position of the containing windows and views. + */ + float getRawXOffset() const; + + /** + * Get the Y offset of this motion event relative to the origin of the raw coordinate space. + * + * In practice, this is the delta that was added to the raw screen coordinates (i.e. in logical + * display space) to adjust for the absolute position of the containing windows and views. + */ + float getRawYOffset() const; + void scale(float globalScaleFactor); // Set 3x3 perspective matrix transformation. diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 9e0ce1db33..63f5ca4c58 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -690,6 +690,18 @@ void MotionEvent::offsetLocation(float xOffset, float yOffset) { mTransform.set(currXOffset + xOffset, currYOffset + yOffset); } +float MotionEvent::getRawXOffset() const { + // This is equivalent to the x-coordinate of the point that the origin of the raw coordinate + // space maps to. + return (mTransform * mRawTransform.inverse()).tx(); +} + +float MotionEvent::getRawYOffset() const { + // This is equivalent to the y-coordinate of the point that the origin of the raw coordinate + // space maps to. + return (mTransform * mRawTransform.inverse()).ty(); +} + void MotionEvent::scale(float globalScaleFactor) { mTransform.set(mTransform.tx() * globalScaleFactor, mTransform.ty() * globalScaleFactor); mRawTransform.set(mRawTransform.tx() * globalScaleFactor, diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index a9655730fc..ba09ece925 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -358,8 +358,10 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState()); ASSERT_EQ(MotionClassification::NONE, event->getClassification()); EXPECT_EQ(mTransform, event->getTransform()); - ASSERT_EQ(X_OFFSET, event->getXOffset()); - ASSERT_EQ(Y_OFFSET, event->getYOffset()); + ASSERT_NEAR((-RAW_X_OFFSET / RAW_X_SCALE) * X_SCALE + X_OFFSET, event->getRawXOffset(), + EPSILON); + ASSERT_NEAR((-RAW_Y_OFFSET / RAW_Y_SCALE) * Y_SCALE + Y_OFFSET, event->getRawYOffset(), + EPSILON); ASSERT_EQ(2.0f, event->getXPrecision()); ASSERT_EQ(2.1f, event->getYPrecision()); ASSERT_EQ(ARBITRARY_DOWN_TIME, event->getDownTime()); @@ -557,22 +559,26 @@ TEST_F(MotionEventTest, CopyFrom_DoNotKeepHistory) { TEST_F(MotionEventTest, OffsetLocation) { MotionEvent event; initializeEventWithHistory(&event); + const float xOffset = event.getRawXOffset(); + const float yOffset = event.getRawYOffset(); event.offsetLocation(5.0f, -2.0f); - ASSERT_EQ(X_OFFSET + 5.0f, event.getXOffset()); - ASSERT_EQ(Y_OFFSET - 2.0f, event.getYOffset()); + ASSERT_EQ(xOffset + 5.0f, event.getRawXOffset()); + ASSERT_EQ(yOffset - 2.0f, event.getRawYOffset()); } TEST_F(MotionEventTest, Scale) { MotionEvent event; initializeEventWithHistory(&event); const float unscaledOrientation = event.getOrientation(0); + const float unscaledXOffset = event.getRawXOffset(); + const float unscaledYOffset = event.getRawYOffset(); event.scale(2.0f); - ASSERT_EQ(X_OFFSET * 2, event.getXOffset()); - ASSERT_EQ(Y_OFFSET * 2, event.getYOffset()); + ASSERT_EQ(unscaledXOffset * 2, event.getRawXOffset()); + ASSERT_EQ(unscaledYOffset * 2, event.getRawYOffset()); ASSERT_NEAR((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0), EPSILON); ASSERT_NEAR((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0), EPSILON); diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 35430207f9..30f9a42519 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -135,8 +135,10 @@ void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& mo EXPECT_EQ(args.buttonState, motionEvent.getButtonState()); EXPECT_EQ(args.classification, motionEvent.getClassification()); EXPECT_EQ(args.transform, motionEvent.getTransform()); - EXPECT_EQ(args.xOffset, motionEvent.getXOffset()); - EXPECT_EQ(args.yOffset, motionEvent.getYOffset()); + EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset, + motionEvent.getRawXOffset(), EPSILON); + EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset, + motionEvent.getRawYOffset(), EPSILON); EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision()); EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision()); EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON); -- GitLab From 2b2d1bf66ae1fdc5f2db3d9fadf916ccb22d4a7b Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 5 Mar 2024 16:27:56 +0000 Subject: [PATCH 020/465] InputTracer: Trace injected events with history as separate events If an injected event is batched, it is split into separate events and each historical event is injected individually. When this happens, the historical events were not being traced, which is a bug. Trace them. It is difficult to test batching from InputDispatcher_tests because we are polling for events rather than responding to them, so avoid adding a test for now. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I7062f97181bcde614e43e68e127583690efa70b2 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 057b996a23..1725e48f7f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4815,6 +4815,10 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev pointerCount)); transformMotionEntryForInjectionLocked(*nextInjectedEntry, motionEvent.getTransform()); + if (mTracer) { + nextInjectedEntry->traceTracker = + mTracer->traceInboundEvent(*nextInjectedEntry); + } injectedEntries.push(std::move(nextInjectedEntry)); } break; -- GitLab From 9f20708f66db424a1a7246be38d1da4ba72a11b7 Mon Sep 17 00:00:00 2001 From: Kevin Lubick Date: Wed, 6 Mar 2024 13:33:17 +0000 Subject: [PATCH 021/465] [native] Migrate deprecated GrBackendSemaphore methods See https://skia-review.googlesource.com/c/skia/+/781236 and https://skia-review.googlesource.com/c/skia/+/821978 Change-Id: I15ce6998c9766fc133baed3f153f691c875f0a70 Bug: b/293490566 --- libs/renderengine/skia/SkiaVkRenderEngine.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index eb7a9d5bfa..feb76a1ccd 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -766,8 +767,7 @@ void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd base::unique_fd fenceDup(dupedFd); VkSemaphore waitSemaphore = getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release()); - GrBackendSemaphore beSemaphore; - beSemaphore.initVulkan(waitSemaphore); + GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore); grContext->wait(1, &beSemaphore, true /* delete after wait */); } @@ -775,8 +775,7 @@ base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { VulkanInterface& vi = getVulkanInterface(isProtected()); VkSemaphore semaphore = vi.createExportableSemaphore(); - GrBackendSemaphore backendSemaphore; - backendSemaphore.initVulkan(semaphore); + GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore); GrFlushInfo flushInfo; DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; -- GitLab From c13922027fada1919875c0e16f699dbde69e1f0b Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Wed, 6 Mar 2024 19:10:00 +0000 Subject: [PATCH 022/465] Always capitalize hdr and sdr Bug: 327182552 Test: builds Change-Id: Ic8dd3ee6e649e493512a32956ac6d97e2627d25f --- include/android/surface_control.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index 321737e226..bf1f2e90ad 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -532,7 +532,7 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio * using this API for formats that encode an HDR/SDR ratio as part of generating the buffer. * * @param surface_control The layer whose extended range brightness is being specified - * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as + * @param currentBufferRatio The current HDR/SDR ratio of the current buffer as represented as * peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the * buffer was rendered with a target SDR whitepoint of 100nits and a max * display brightness of 200nits, this should be set to 2.0f. @@ -546,7 +546,7 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio * * Must be finite && >= 1.0f * - * @param desiredRatio The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits / + * @param desiredRatio The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits / * targetSdrWhitePointInNits. This can be used to communicate the max desired * brightness range. This is similar to the "max luminance" value in other * HDR metadata formats, but represented as a ratio of the target SDR whitepoint @@ -579,13 +579,13 @@ void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* transac float desiredRatio) __INTRODUCED_IN(__ANDROID_API_U__); /** - * Sets the desired hdr headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness, + * Sets the desired HDR headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness, * prefer using this API for formats that conform to HDR standards like HLG or HDR10, that do not * communicate a HDR/SDR ratio as part of generating the buffer. * - * @param surface_control The layer whose desired hdr headroom is being specified + * @param surface_control The layer whose desired HDR headroom is being specified * - * @param desiredHeadroom The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits / + * @param desiredHeadroom The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits / * targetSdrWhitePointInNits. This can be used to communicate the max * desired brightness range of the panel. The system may not be able to, or * may choose not to, deliver the requested range. -- GitLab From 990d8713a9913136dba9eb57f3b351edaa00be26 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 5 Mar 2024 00:31:36 +0000 Subject: [PATCH 023/465] PointerChoreographer: Remove ability to create mouse controllers OTF In the following CL, we made a change that results in the mouse cursor position being valid whenever there is a mouse or touchpad connected: I55898a3de1beb0f83f5da199521f26a886fb596c This means we are no longer depending on creating mouse controllers on-the-fly based on the input events. Remove the logic that creates mouse controllers on-the-fly. Bug: 327717240 Test: atest inputflinger_tests Change-Id: I0fa10ef48055d80136083a1c0ab23522f6683fdc --- services/inputflinger/PointerChoreographer.cpp | 18 +++++++----------- services/inputflinger/PointerChoreographer.h | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 3ac4285304..3e7c1c71ef 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -104,7 +104,7 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio << args.dump(); } - auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId); + auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); @@ -124,7 +124,7 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio } NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) { - auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId); + auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); NotifyMotionArgs newArgs(args); newArgs.displayId = displayId; @@ -308,17 +308,13 @@ int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisp return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId; } -std::pair -PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) { +std::pair PointerChoreographer::ensureMouseControllerLocked( + int32_t associatedDisplayId) { const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId); - // Get the mouse pointer controller for the display, or create one if it doesn't exist. - auto [it, emplaced] = - mMousePointersByDisplay.try_emplace(displayId, - getMouseControllerConstructor(displayId)); - if (emplaced) { - notifyPointerDisplayIdChangedLocked(); - } + auto it = mMousePointersByDisplay.find(displayId); + LOG_ALWAYS_FATAL_IF(it == mMousePointersByDisplay.end(), + "There is no mouse controller created for display %d", displayId); return {displayId, *it->second}; } diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index 6aab3aade0..f46038ef73 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -113,7 +113,7 @@ private: void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock); const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); - std::pair getDisplayIdAndMouseControllerLocked( + std::pair ensureMouseControllerLocked( int32_t associatedDisplayId) REQUIRES(mLock); InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock); bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock); -- GitLab From 5a51a2280743605a51e2f9d632077e9297276520 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 5 Mar 2024 03:54:00 +0000 Subject: [PATCH 024/465] PointerChoreographer: Do not call the policy with the lock held Since there is already precedent for InputReader to interact with the policy with its lock held, we must not do the same from other components, since it can result in deadlocks. In this CL, we ensure that the PointerChoreographer does not interact with the policy while holding its lock. The exception is the use of the factory method for PointerController, which is only part of the policy to work around dependency issues with graphics libraries. Bug: 327717240 Test: atest inputflinger_tests Change-Id: Ib81d72898a212275d95f9d84d89a16e7172e108e --- .../inputflinger/PointerChoreographer.cpp | 121 ++++++++++++------ services/inputflinger/PointerChoreographer.h | 6 +- .../PointerChoreographerPolicyInterface.h | 6 + 3 files changed, 91 insertions(+), 42 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 3e7c1c71ef..9db3574389 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -26,6 +26,7 @@ namespace android { namespace { + bool isFromMouse(const NotifyMotionArgs& args) { return isFromSource(args.source, AINPUT_SOURCE_MOUSE) && args.pointerProperties[0].toolType == ToolType::MOUSE; @@ -44,13 +45,23 @@ bool isHoverAction(int32_t action) { bool isStylusHoverEvent(const NotifyMotionArgs& args) { return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action); } + +inline void notifyPointerDisplayChange(std::optional> change, + PointerChoreographerPolicyInterface& policy) { + if (!change) { + return; + } + const auto& [displayId, cursorPosition] = *change; + policy.notifyPointerDisplayIdChanged(displayId, cursorPosition); +} + } // namespace // --- PointerChoreographer --- PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, PointerChoreographerPolicyInterface& policy) - : mTouchControllerConstructor([this]() REQUIRES(mLock) { + : mTouchControllerConstructor([this]() { return mPolicy.createPointerController( PointerControllerInterface::ControllerType::TOUCH); }), @@ -62,10 +73,16 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, mStylusPointerIconEnabled(false) {} void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { - std::scoped_lock _l(mLock); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + + mInputDeviceInfos = args.inputDeviceInfos; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock - mInputDeviceInfos = args.inputDeviceInfos; - updatePointerControllersLocked(); + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); mNextListener.notify(args); } @@ -329,7 +346,7 @@ bool PointerChoreographer::canUnfadeOnDisplay(int32_t displayId) { return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end(); } -void PointerChoreographer::updatePointerControllersLocked() { +PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() { std::set mouseDisplaysToKeep; std::set touchDevicesToKeep; std::set stylusDevicesToKeep; @@ -378,11 +395,12 @@ void PointerChoreographer::updatePointerControllersLocked() { mInputDeviceInfos.end(); }); - // Notify the policy if there's a change on the pointer display ID. - notifyPointerDisplayIdChangedLocked(); + // Check if we need to notify the policy if there's a change on the pointer display ID. + return calculatePointerDisplayChangeToNotify(); } -void PointerChoreographer::notifyPointerDisplayIdChangedLocked() { +PointerChoreographer::PointerDisplayChange +PointerChoreographer::calculatePointerDisplayChangeToNotify() { int32_t displayIdToNotify = ADISPLAY_ID_NONE; FloatPoint cursorPosition = {0, 0}; if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId); @@ -394,38 +412,49 @@ void PointerChoreographer::notifyPointerDisplayIdChangedLocked() { displayIdToNotify = pointerController->getDisplayId(); cursorPosition = pointerController->getPosition(); } - if (mNotifiedPointerDisplayId == displayIdToNotify) { - return; + return {}; } - mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition); mNotifiedPointerDisplayId = displayIdToNotify; + return {{displayIdToNotify, cursorPosition}}; } void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) { - std::scoped_lock _l(mLock); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); - mDefaultMouseDisplayId = displayId; - updatePointerControllersLocked(); + mDefaultMouseDisplayId = displayId; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } void PointerChoreographer::setDisplayViewports(const std::vector& viewports) { - std::scoped_lock _l(mLock); - for (const auto& viewport : viewports) { - const int32_t displayId = viewport.displayId; - if (const auto it = mMousePointersByDisplay.find(displayId); - it != mMousePointersByDisplay.end()) { - it->second->setDisplayViewport(viewport); - } - for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) { - const InputDeviceInfo* info = findInputDeviceLocked(deviceId); - if (info && info->getAssociatedDisplayId() == displayId) { - stylusPointerController->setDisplayViewport(viewport); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + for (const auto& viewport : viewports) { + const int32_t displayId = viewport.displayId; + if (const auto it = mMousePointersByDisplay.find(displayId); + it != mMousePointersByDisplay.end()) { + it->second->setDisplayViewport(viewport); + } + for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) { + const InputDeviceInfo* info = findInputDeviceLocked(deviceId); + if (info && info->getAssociatedDisplayId() == displayId) { + stylusPointerController->setDisplayViewport(viewport); + } } } - } - mViewports = viewports; - notifyPointerDisplayIdChangedLocked(); + mViewports = viewports; + pointerDisplayChange = calculatePointerDisplayChangeToNotify(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } std::optional PointerChoreographer::getViewportForPointerDevice( @@ -449,21 +478,33 @@ FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) { } void PointerChoreographer::setShowTouchesEnabled(bool enabled) { - std::scoped_lock _l(mLock); - if (mShowTouchesEnabled == enabled) { - return; - } - mShowTouchesEnabled = enabled; - updatePointerControllersLocked(); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + if (mShowTouchesEnabled == enabled) { + return; + } + mShowTouchesEnabled = enabled; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) { - std::scoped_lock _l(mLock); - if (mStylusPointerIconEnabled == enabled) { - return; - } - mStylusPointerIconEnabled = enabled; - updatePointerControllersLocked(); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + if (mStylusPointerIconEnabled == enabled) { + return; + } + mStylusPointerIconEnabled = enabled; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } bool PointerChoreographer::setPointerIcon( diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index f46038ef73..db1488b546 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -109,8 +109,10 @@ public: void dump(std::string& dump) override; private: - void updatePointerControllersLocked() REQUIRES(mLock); - void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock); + using PointerDisplayChange = + std::optional>; + [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock); + [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock); const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); std::pair ensureMouseControllerLocked( diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h index 8b47b555e5..462aedc539 100644 --- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h +++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h @@ -25,6 +25,9 @@ namespace android { * * This is the interface that PointerChoreographer uses to talk to Window Manager and other * system components. + * + * NOTE: In general, the PointerChoreographer must not interact with the policy while + * holding any locks. */ class PointerChoreographerPolicyInterface { public: @@ -37,6 +40,9 @@ public: * for and runnable on the host, the PointerController implementation must be in a separate * library, libinputservice, that has the additional dependencies. The PointerController * will be mocked when testing PointerChoreographer. + * + * Since this is a factory method used to work around dependencies, it will not interact with + * other input components and may be called with the PointerChoreographer lock held. */ virtual std::shared_ptr createPointerController( PointerControllerInterface::ControllerType type) = 0; -- GitLab From 52ad61cfb42a18844da34df2cd5567736e6726e9 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 6 Mar 2024 11:39:35 -0800 Subject: [PATCH 025/465] SF: should not boost based on global touch when have category Fixes a bug where the SF GlobalSignals.touch boost was still triggering when there were explicit category votes such as Normal. When there are explicit category votes, that old-style touch boost should no longer trigger. Bug: 327015285 Test: atest libsurfaceflinger_unittest Change-Id: Ic87fad95089719297cebf403c0ca7717cd2741f0 --- .../Scheduler/RefreshRateSelector.cpp | 6 +- .../unittests/RefreshRateSelectorTest.cpp | 148 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index ffd3463296..8a82405a45 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -834,13 +834,16 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector 0; if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && + touchBoostForCategory && scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) { ALOGV("Touch Boost"); ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])", diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 0a6e3054dd..b655f25d44 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -1665,6 +1665,7 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighH lr1.frameRateCategory = FrameRateCategory::HighHint; lr1.name = "ExplicitCategory HighHint"; lr2.vote = LayerVoteType::ExplicitExactOrMultiple; + lr2.frameRateCategory = FrameRateCategory::Default; lr2.desiredRefreshRate = 30_Hz; lr2.name = "30Hz ExplicitExactOrMultiple"; actualRankedFrameRates = selector.getRankedFrameRates(layers); @@ -1691,6 +1692,153 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighH EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); } + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::Heuristic; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz Heuristic"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::Min; + lr2.name = "Min"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::HighHint; + lr1.name = "ExplicitCategory HighHint"; + lr2.vote = LayerVoteType::Max; + lr2.name = "Max"; + actualRankedFrameRates = selector.getRankedFrameRates(layers); + // Gets touch boost + EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps); + EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId()); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); +} + +TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_TouchBoost) { + auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60); + + std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; + auto& lr1 = layers[0]; + auto& lr2 = layers[1]; + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::NoVote; + lr2.name = "NoVote"; + auto actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + + // No touch boost, for example a game that uses setFrameRate(30, default compatibility). + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::ExplicitDefault; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitDefault"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::ExplicitCategory; + lr2.frameRateCategory = FrameRateCategory::HighHint; + lr2.name = "ExplicitCategory HighHint"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::ExplicitCategory; + lr2.frameRateCategory = FrameRateCategory::Low; + lr2.name = "ExplicitCategory Low"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::ExplicitExactOrMultiple; + lr2.frameRateCategory = FrameRateCategory::Default; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitExactOrMultiple"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::ExplicitExact; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitExact"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + if (selector.supportsAppFrameRateOverrideByContent()) { + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, + actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + } else { + EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz, + actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + } + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::Min; + lr2.name = "Min"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::Max; + lr2.name = "Max"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::Heuristic; + lr2.name = "30Hz Heuristic"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch); + + lr1.vote = LayerVoteType::ExplicitCategory; + lr1.frameRateCategory = FrameRateCategory::Normal; + lr1.name = "ExplicitCategory Normal"; + lr2.vote = LayerVoteType::ExplicitGte; + lr2.desiredRefreshRate = 30_Hz; + lr2.name = "30Hz ExplicitGte"; + actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true}); + EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode); + EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch); } TEST_P(RefreshRateSelectorTest, -- GitLab From efb131e3e11bfc12df020d4100b67c49be7c1f27 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 16 Feb 2024 10:57:40 -0500 Subject: [PATCH 026/465] SF: Remove StartPropertySetThread Reimplement with std::async and std::future to cut the boilerplate, and remove the dependency on the deprecated Thread class from libutils. Bug: 324366212 Test: Boot. Test: adb shell killall system_server Change-Id: Id39dab356cc267f1575bba4010f3b161c1d0923c --- services/surfaceflinger/Android.bp | 1 - .../surfaceflinger/StartPropertySetThread.cpp | 41 -------------- .../surfaceflinger/StartPropertySetThread.h | 46 --------------- services/surfaceflinger/SurfaceFlinger.cpp | 56 ++++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 14 +++-- .../SurfaceFlingerDefaultFactory.cpp | 6 -- .../SurfaceFlingerDefaultFactory.h | 1 - .../surfaceflinger/SurfaceFlingerFactory.h | 3 - .../tests/unittests/TestableSurfaceFlinger.h | 5 -- 9 files changed, 39 insertions(+), 134 deletions(-) delete mode 100644 services/surfaceflinger/StartPropertySetThread.cpp delete mode 100644 services/surfaceflinger/StartPropertySetThread.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index d77180362b..2a4327f696 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -210,7 +210,6 @@ filegroup { "Scheduler/VsyncModulator.cpp", "Scheduler/VsyncSchedule.cpp", "ScreenCaptureOutput.cpp", - "StartPropertySetThread.cpp", "SurfaceFlinger.cpp", "SurfaceFlingerDefaultFactory.cpp", "Tracing/LayerDataSource.cpp", diff --git a/services/surfaceflinger/StartPropertySetThread.cpp b/services/surfaceflinger/StartPropertySetThread.cpp deleted file mode 100644 index f42cd53e0a..0000000000 --- a/services/surfaceflinger/StartPropertySetThread.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ - -#include -#include "StartPropertySetThread.h" - -namespace android { - -StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue): - Thread(false), mTimestampPropertyValue(timestampPropertyValue) {} - -status_t StartPropertySetThread::Start() { - return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL); -} - -bool StartPropertySetThread::threadLoop() { - // Set property service.sf.present_timestamp, consumer need check its readiness - property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0"); - // Clear BootAnimation exit flag - property_set("service.bootanim.exit", "0"); - property_set("service.bootanim.progress", "0"); - // Start BootAnimation if not started - property_set("ctl.start", "bootanim"); - // Exit immediately - return false; -} - -} // namespace android diff --git a/services/surfaceflinger/StartPropertySetThread.h b/services/surfaceflinger/StartPropertySetThread.h deleted file mode 100644 index bbdcde2809..0000000000 --- a/services/surfaceflinger/StartPropertySetThread.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ - -#ifndef ANDROID_STARTBOOTANIMTHREAD_H -#define ANDROID_STARTBOOTANIMTHREAD_H - -#include - -#include -#include - -namespace android { - -class StartPropertySetThread : public Thread { -// Boot animation is triggered via calls to "property_set()" which can block -// if init's executing slow operation such as 'mount_all --late' (currently -// happening 1/10th with fsck) concurrently. Running in a separate thread -// allows to pursue the SurfaceFlinger's init process without blocking. -// see b/34499826. -// Any property_set() will block during init stage so need to be offloaded -// to this thread. see b/63844978. -public: - explicit StartPropertySetThread(bool timestampPropertyValue); - status_t Start(); -private: - virtual bool threadLoop(); - static constexpr const char* kTimestampProperty = "service.sf.present_timestamp"; - const bool mTimestampPropertyValue; -}; - -} - -#endif // ANDROID_STARTBOOTANIMTHREAD_H diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 2501f4b7f7..50cd45b012 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -153,7 +153,6 @@ #include "Scheduler/VsyncConfiguration.h" #include "Scheduler/VsyncModulator.h" #include "ScreenCaptureOutput.h" -#include "StartPropertySetThread.h" #include "SurfaceFlingerProperties.h" #include "TimeStats/TimeStats.h" #include "TunnelModeEnabledReporter.h" @@ -566,7 +565,14 @@ void SurfaceFlinger::binderDied(const wp&) { initializeDisplays(); })); - startBootAnim(); + std::lock_guard lock(mInitBootPropsFutureMutex); + if (!mInitBootPropsFuture.valid()) { + mInitBootPropsFuture = + std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this); + } + + mInitBootPropsFuture.wait(); + mInitBootPropsFuture = {}; } void SurfaceFlinger::run() { @@ -722,13 +728,15 @@ void SurfaceFlinger::bootFinished() { } mBootFinished = true; FlagManager::getMutableInstance().markBootCompleted(); - if (mStartPropertySetThread->join() != NO_ERROR) { - ALOGE("Join StartPropertySetThread failed!"); - } + if (std::lock_guard lock(mInitBootPropsFutureMutex); mInitBootPropsFuture.valid()) { + mInitBootPropsFuture.wait(); + mInitBootPropsFuture = {}; + } if (mRenderEnginePrimeCacheFuture.valid()) { - mRenderEnginePrimeCacheFuture.get(); + mRenderEnginePrimeCacheFuture.wait(); } + const nsecs_t now = systemTime(); const nsecs_t duration = now - mBootTime; ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) ); @@ -816,8 +824,6 @@ void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& bui } } -// Do not call property_set on main thread which will be blocked by init -// Use StartPropertySetThread instead. void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { ATRACE_CALL(); ALOGI( "SurfaceFlinger's main thread ready to run. " @@ -928,18 +934,28 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { } } - // Inform native graphics APIs whether the present timestamp is supported: - - mStartPropertySetThread = getFactory().createStartPropertySetThread(mHasReliablePresentFences); - - if (mStartPropertySetThread->Start() != NO_ERROR) { - ALOGE("Run StartPropertySetThread failed!"); + // Avoid blocking the main thread on `init` to set properties. + if (std::lock_guard lock(mInitBootPropsFutureMutex); !mInitBootPropsFuture.valid()) { + mInitBootPropsFuture = + std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this); } initTransactionTraceWriter(); ALOGV("Done initializing"); } +// During boot, offload `initBootProperties` to another thread. `property_set` depends on +// `property_service`, which may be delayed by slow operations like `mount_all --late` in +// the `init` process. See b/34499826 and b/63844978. +void SurfaceFlinger::initBootProperties() { + property_set("service.sf.present_timestamp", mHasReliablePresentFences ? "1" : "0"); + + // Reset and (if needed) start BootAnimation. + property_set("service.bootanim.exit", "0"); + property_set("service.bootanim.progress", "0"); + property_set("ctl.start", "bootanim"); +} + void SurfaceFlinger::initTransactionTraceWriter() { if (!mTransactionTracing) { return; @@ -979,18 +995,6 @@ void SurfaceFlinger::readPersistentProperties() { static_cast(base::GetIntProperty("persist.sys.sf.color_mode"s, 0)); } -void SurfaceFlinger::startBootAnim() { - // Start boot animation service by setting a property mailbox - // if property setting thread is already running, Start() will be just a NOP - mStartPropertySetThread->Start(); - // Wait until property was set - if (mStartPropertySetThread->join() != NO_ERROR) { - ALOGE("Join StartPropertySetThread failed!"); - } -} - -// ---------------------------------------------------------------------------- - status_t SurfaceFlinger::getSupportedFrameTimestamps( std::vector* outSupported) const { *outSupported = { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 005f2e6344..b9ea0c3c67 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -550,7 +550,7 @@ private: bool hasListenerCallbacks, const std::vector& listenerCallbacks, uint64_t transactionId, const std::vector& mergedTransactionIds) override; void bootFinished(); - virtual status_t getSupportedFrameTimestamps(std::vector* outSupported) const; + status_t getSupportedFrameTimestamps(std::vector* outSupported) const; sp createDisplayEventConnection( gui::ISurfaceComposer::VsyncSource vsyncSource = gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, @@ -871,9 +871,6 @@ private: // Traverse through all the layers and compute and cache its bounds. void computeLayerBounds(); - // Boot animation, on/off animations and screen capture - void startBootAnim(); - bool layersHasProtectedLayer(const std::vector>>& layers) const; void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize, @@ -1184,10 +1181,17 @@ private: ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const REQUIRES(mStateLock); void traverseLegacyLayers(const LayerVector::Visitor& visitor) const; + + void initBootProperties(); void initTransactionTraceWriter(); - sp mStartPropertySetThread; + surfaceflinger::Factory& mFactory; pid_t mPid; + + // TODO: b/328459745 - Encapsulate in a SystemProperties object. + std::mutex mInitBootPropsFutureMutex; + std::future mInitBootPropsFuture GUARDED_BY(mInitBootPropsFutureMutex); + std::future mRenderEnginePrimeCacheFuture; // mStateLock has conventions related to the current thread, because only diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp index 7e6894d3f7..50b167d5d7 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp @@ -26,7 +26,6 @@ #include "FrameTracer/FrameTracer.h" #include "Layer.h" #include "NativeWindowSurface.h" -#include "StartPropertySetThread.h" #include "SurfaceFlingerDefaultFactory.h" #include "SurfaceFlingerProperties.h" @@ -53,11 +52,6 @@ std::unique_ptr DefaultFactory::createVsyncConfig } } -sp DefaultFactory::createStartPropertySetThread( - bool timestampPropertyValue) { - return sp::make(timestampPropertyValue); -} - sp DefaultFactory::createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) { return sp::make(creationArgs); } diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h index 2c6de0e113..540dec832e 100644 --- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h +++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h @@ -29,7 +29,6 @@ public: std::unique_ptr createHWComposer(const std::string& serviceName) override; std::unique_ptr createVsyncConfiguration( Fps currentRefreshRate) override; - sp createStartPropertySetThread(bool timestampPropertyValue) override; sp createDisplayDevice(DisplayDeviceCreationArgs&) override; sp createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, uint64_t usage, diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h index f310c4ac53..f1fbf013c7 100644 --- a/services/surfaceflinger/SurfaceFlingerFactory.h +++ b/services/surfaceflinger/SurfaceFlingerFactory.h @@ -39,7 +39,6 @@ class IGraphicBufferConsumer; class IGraphicBufferProducer; class Layer; class LayerFE; -class StartPropertySetThread; class SurfaceFlinger; class TimeStats; @@ -71,8 +70,6 @@ public: virtual std::unique_ptr createVsyncConfiguration( Fps currentRefreshRate) = 0; - virtual sp createStartPropertySetThread( - bool timestampPropertyValue) = 0; virtual sp createDisplayDevice(DisplayDeviceCreationArgs&) = 0; virtual sp createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount, diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index bce7729d80..82023b092a 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -43,7 +43,6 @@ #include "RenderArea.h" #include "Scheduler/MessageQueue.h" #include "Scheduler/RefreshRateSelector.h" -#include "StartPropertySetThread.h" #include "SurfaceFlinger.h" #include "TestableScheduler.h" #include "mock/DisplayHardware/MockComposer.h" @@ -94,10 +93,6 @@ public: return std::make_unique(); } - sp createStartPropertySetThread(bool timestampPropertyValue) override { - return sp::make(timestampPropertyValue); - } - sp createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) override { return sp::make(creationArgs); } -- GitLab From b0fdfe7095a39f2bfdafcc5caa912b440a8f6894 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 21 Feb 2024 10:42:23 -0500 Subject: [PATCH 027/465] SF: Add property to skip boot animation Bug: 324366212 Test: setprop debug.sf.boot_animation false Change-Id: I8e0076d35566a0d7326dd1e8fe7b5d58bafad67a --- services/surfaceflinger/SurfaceFlinger.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 50cd45b012..be3c1d89d0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -950,10 +950,12 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { void SurfaceFlinger::initBootProperties() { property_set("service.sf.present_timestamp", mHasReliablePresentFences ? "1" : "0"); - // Reset and (if needed) start BootAnimation. - property_set("service.bootanim.exit", "0"); - property_set("service.bootanim.progress", "0"); - property_set("ctl.start", "bootanim"); + if (base::GetBoolProperty("debug.sf.boot_animation"s, true)) { + // Reset and (if needed) start BootAnimation. + property_set("service.bootanim.exit", "0"); + property_set("service.bootanim.progress", "0"); + property_set("ctl.start", "bootanim"); + } } void SurfaceFlinger::initTransactionTraceWriter() { -- GitLab From 20024aaf4dda8272cca5943afe71d1bc0f879cb6 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 5 Mar 2024 01:32:49 +0000 Subject: [PATCH 028/465] Revert^2 "SF: Introduce VsyncTimeline to VsyncPredictor" Add the concept of timeline freezing when switching render rate. This allow us to change render rates in sync with the app and remain jank free across render rate changes. Bug: 326599221 Test: Run TouchLatency, change render rate and examine Perfetto trace Change-Id: Ic91d482593d6b978c28e3c6c0d19e2c055d5f149 --- .../Scheduler/VSyncDispatchTimerQueue.cpp | 71 +++-- .../Scheduler/VSyncDispatchTimerQueue.h | 4 +- .../Scheduler/VSyncPredictor.cpp | 298 +++++++++++++----- .../surfaceflinger/Scheduler/VSyncPredictor.h | 60 +++- .../surfaceflinger/Scheduler/VSyncTracker.h | 6 +- .../Scheduler/VsyncSchedule.cpp | 4 +- .../surfaceflinger/Scheduler/VsyncSchedule.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 12 +- .../tests/unittests/SchedulerTest.cpp | 11 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 6 +- .../tests/unittests/VSyncPredictorTest.cpp | 101 ++++-- .../tests/unittests/mock/MockVSyncTracker.h | 4 +- 12 files changed, 408 insertions(+), 171 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 84ccf8e938..6d6b70d198 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -44,6 +44,17 @@ ScheduleResult getExpectedCallbackTime(nsecs_t nextVsyncTime, TimePoint::fromNs(nextVsyncTime)}; } +void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) { + if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) { + return; + } + + ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ", + ns2us(*entry.wakeupTime() - now), "us; VSYNC in ", + ns2us(*entry.targetVsync() - now), "us"); + ATRACE_FORMAT_INSTANT(trace.c_str()); +} + } // namespace VSyncDispatch::~VSyncDispatch() = default; @@ -87,6 +98,7 @@ std::optional VSyncDispatchTimerQueueEntry::targetVsync() const { ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker, nsecs_t now) { + ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule"); auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync, now + timing.workDuration + @@ -98,6 +110,8 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance)); bool const wouldSkipAWakeup = mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance))); + ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), + wouldSkipAVsyncTarget, wouldSkipAWakeup); if (FlagManager::getInstance().dont_skip_on_early_ro()) { if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { nextVsyncTime = mArmedInfo->mActualVsyncTime; @@ -122,7 +136,7 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate( VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) { mWorkloadUpdateInfo = timing; - const auto armedInfo = update(tracker, now, timing, mArmedInfo); + const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo); return {TimePoint::fromNs(armedInfo.mActualWakeupTime), TimePoint::fromNs(armedInfo.mActualVsyncTime)}; } @@ -140,11 +154,13 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, bool const nextVsyncTooClose = mLastDispatchTime && (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod; if (alreadyDispatchedForVsync) { + ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance, *mLastDispatchTime); } if (nextVsyncTooClose) { + ATRACE_FORMAT_INSTANT("nextVsyncTooClose"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod, *mLastDispatchTime + currentPeriod); } @@ -152,9 +168,11 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, return nextVsyncTime; } -auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, - VSyncDispatch::ScheduleTiming timing, - std::optional armedInfo) const -> ArmingInfo { +auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now, + VSyncDispatch::ScheduleTiming timing, + std::optional armedInfo) const + -> ArmingInfo { + ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo"); const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration; const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync); @@ -165,29 +183,39 @@ auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, const auto nextReadyTime = nextVsyncTime - timing.readyDuration; const auto nextWakeupTime = nextReadyTime - timing.workDuration; - bool const wouldSkipAVsyncTarget = - armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); - bool const wouldSkipAWakeup = - armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); - if (FlagManager::getInstance().dont_skip_on_early_ro() && - (wouldSkipAVsyncTarget || wouldSkipAWakeup)) { - return *armedInfo; + if (FlagManager::getInstance().dont_skip_on_early_ro()) { + bool const wouldSkipAVsyncTarget = + armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); + bool const wouldSkipAWakeup = + armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); + ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), + wouldSkipAVsyncTarget, wouldSkipAWakeup); + if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { + return *armedInfo; + } } return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime}; } void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { + ATRACE_NAME("VSyncDispatchTimerQueueEntry::update"); if (!mArmedInfo && !mWorkloadUpdateInfo) { return; } if (mWorkloadUpdateInfo) { + const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration; + const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration; + const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync; + ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64 + " lastVsyncDelta=%" PRId64, + workDelta, readyDelta, lastVsyncDelta); mScheduleTiming = *mWorkloadUpdateInfo; mWorkloadUpdateInfo.reset(); } - mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo); + mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo); } void VSyncDispatchTimerQueueEntry::disarm() { @@ -282,6 +310,7 @@ void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) { void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( nsecs_t now, CallbackMap::const_iterator skipUpdateIt) { + ATRACE_CALL(); std::optional min; std::optional targetVsync; std::optional nextWakeupName; @@ -294,7 +323,10 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( if (it != skipUpdateIt) { callback->update(*mTracker, now); } - auto const wakeupTime = *callback->wakeupTime(); + + traceEntry(*callback, now); + + const auto wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { nextWakeupName = callback->name(); min = wakeupTime; @@ -303,11 +335,6 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (min && min < mIntendedWakeupTime) { - if (ATRACE_ENABLED() && nextWakeupName && targetVsync) { - ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now), - "us; VSYNC in ", ns2us(*targetVsync - now), "us"); - ATRACE_NAME(trace.c_str()); - } setTimer(*min, now); } else { ATRACE_NAME("cancel timer"); @@ -316,6 +343,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } void VSyncDispatchTimerQueue::timerCallback() { + ATRACE_CALL(); struct Invocation { std::shared_ptr callback; nsecs_t vsyncTimestamp; @@ -338,8 +366,9 @@ void VSyncDispatchTimerQueue::timerCallback() { continue; } - auto const readyTime = callback->readyTime(); + traceEntry(*callback, now); + auto const readyTime = callback->readyTime(); auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast(0)); if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) { callback->executing(); @@ -353,6 +382,8 @@ void VSyncDispatchTimerQueue::timerCallback() { } for (auto const& invocation : invocations) { + ftl::Concat trace(ftl::truncated<5>(invocation.callback->name())); + ATRACE_FORMAT("%s: %s", __func__, trace.c_str()); invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp, invocation.deadlineTimestamp); } diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index 252c09ce53..e4ddc03480 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -91,8 +91,8 @@ private: }; nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const; - ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, - std::optional) const; + ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, + std::optional) const; const std::string mName; const VSyncDispatch::Callback mCallback; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 8697696915..2f9dfeaff7 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -45,11 +45,35 @@ using base::StringAppendF; static auto constexpr kMaxPercent = 100u; +namespace { +nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime, + std::optional lastVsyncOpt) { + const auto threshold = model.slope / 2; + + if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { + const auto vsyncDiff = vsyncTime - *lastVsyncOpt; + if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) { + const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; + ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. " + "adjust by %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, + static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, + static_cast(vsyncFixup) / 1e6f); + return vsyncFixup; + } + } + + return 0; +} +} // namespace + VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(ftl::NonNull modePtr, size_t historySize, - size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) - : mId(modePtr->getPhysicalDisplayId()), +VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNull modePtr, + size_t historySize, size_t minimumSamplesForPrediction, + uint32_t outlierTolerancePercent) + : mClock(std::move(clock)), + mId(modePtr->getPhysicalDisplayId()), mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), @@ -147,7 +171,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { mKnownTimestamp = timestamp; } ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago", - (systemTime() - *mKnownTimestamp) / 1e6f); + (mClock->now() - *mKnownTimestamp) / 1e6f); return false; } @@ -250,17 +274,6 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { return true; } -auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { - const auto vsync = snapToVsync(timestamp); - if (!mLastVsyncSequence) return {vsync, 0}; - - const auto [slope, _] = getVSyncPredictionModelLocked(); - const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; - const auto vsyncSequence = lastVsyncSequence + - static_cast(std::round((vsync - lastVsyncTime) / static_cast(slope))); - return {vsync, vsyncSequence}; -} - nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); @@ -298,51 +311,32 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { } nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt) const { + std::optional lastVsyncOpt) { ATRACE_CALL(); std::lock_guard lock(mMutex); - const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; - const auto threshold = currentPeriod / 2; - const auto minFramePeriod = minFramePeriodLocked().ns(); - const auto lastFrameMissed = - lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold; - const nsecs_t baseTime = - FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt - ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold) - : timePoint; - return snapToVsyncAlignedWithRenderRate(baseTime); -} - -nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const { - // update the mLastVsyncSequence for reference point - mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { - if (!mRenderRateOpt) return 0; - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), - *mRenderRateOpt); - if (divisor <= 1) return 0; + const auto now = TimePoint::fromNs(mClock->now()); + purgeTimelines(now); - int mod = mLastVsyncSequence->seq % divisor; - if (mod == 0) return 0; - - // This is actually a bug fix, but guarded with vrr_config since we found it with this - // config - if (FlagManager::getInstance().vrr_config()) { - if (mod < 0) mod += divisor; + std::optional vsyncOpt; + for (auto& timeline : mTimelines) { + vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(), + minFramePeriodLocked(), + snapToVsync(timePoint), mMissedVsync, + lastVsyncOpt); + if (vsyncOpt) { + break; } + } + LOG_ALWAYS_FATAL_IF(!vsyncOpt); - return divisor - mod; - }(); - - if (renderRatePhase == 0) { - return mLastVsyncSequence->vsyncTime; + if (*vsyncOpt > mLastCommittedVsync) { + mLastCommittedVsync = *vsyncOpt; + ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms", + float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f); } - auto const [slope, intercept] = getVSyncPredictionModelLocked(); - const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; - return snapToVsync(approximateNextVsync - slope / 2); + return vsyncOpt->ns(); } /* @@ -353,32 +347,28 @@ nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) cons * isVSyncInPhase(33.3, 30) = false * isVSyncInPhase(50.0, 30) = true */ -bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { +bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) { + if (timePoint == 0) { + return true; + } + std::lock_guard lock(mMutex); - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), - frameRate); - return isVSyncInPhaseLocked(timePoint, static_cast(divisor)); -} + const auto model = getVSyncPredictionModelLocked(); + const nsecs_t period = model.slope; + const nsecs_t justBeforeTimePoint = timePoint - period / 2; + const auto now = TimePoint::fromNs(mClock->now()); + const auto vsync = snapToVsync(justBeforeTimePoint); -bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { - const TimePoint now = TimePoint::now(); - const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float { - return ticks(TimePoint::fromNs(timePoint) - now); - }; - ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint), - divisor); + purgeTimelines(now); - if (divisor <= 1 || timePoint == 0) { - return true; + for (auto& timeline : mTimelines) { + if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) { + return timeline.isVSyncInPhase(model, vsync, frameRate); + } } - const nsecs_t period = mRateMap[idealPeriod()].slope; - const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); - ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, - getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); - return vsyncSequence.seq % divisor == 0; + // The last timeline should always be valid + return mTimelines.back().isVSyncInPhase(model, vsync, frameRate); } void VSyncPredictor::setRenderRate(Fps renderRate) { @@ -386,6 +376,9 @@ void VSyncPredictor::setRenderRate(Fps renderRate) { ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str()); std::lock_guard lock(mMutex); mRenderRateOpt = renderRate; + mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); + mTimelines.emplace_back(mIdealPeriod, renderRate); + purgeTimelines(TimePoint::fromNs(mClock->now())); } void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { @@ -415,8 +408,9 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { clearTimestamps(); } -void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, - TimePoint lastConfirmedPresentTime) { +Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, + TimePoint lastConfirmedPresentTime) { + ATRACE_CALL(); const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; const auto threshold = currentPeriod / 2; const auto minFramePeriod = minFramePeriodLocked().ns(); @@ -442,17 +436,20 @@ void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, if (!mPastExpectedPresentTimes.empty()) { const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime); if (phase > 0ns) { - if (mLastVsyncSequence) { - mLastVsyncSequence->vsyncTime += phase.ns(); + for (auto& timeline : mTimelines) { + timeline.shiftVsyncSequence(phase); } mPastExpectedPresentTimes.clear(); + return phase; } } + + return 0ns; } void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) { - ATRACE_CALL(); + ATRACE_NAME("VSyncPredictor::onFrameBegin"); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -482,11 +479,14 @@ void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, } } - ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + if (phase > 0ns) { + mMissedVsync = {expectedPresentTime, minFramePeriodLocked()}; + } } void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { - ATRACE_CALL(); + ATRACE_NAME("VSyncPredictor::onFrameMissed"); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -496,14 +496,15 @@ void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { const auto lastConfirmedPresentTime = TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod); - ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); - mLastMissedVsync = expectedPresentTime; + const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + if (phase > 0ns) { + mMissedVsync = {expectedPresentTime, Duration::fromNs(0)}; + } } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { std::lock_guard lock(mMutex); - const auto model = VSyncPredictor::getVSyncPredictionModelLocked(); - return {model.slope, model.intercept}; + return VSyncPredictor::getVSyncPredictionModelLocked(); } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { @@ -524,6 +525,11 @@ void VSyncPredictor::clearTimestamps() { mTimestamps.clear(); mLastTimestampIndex = 0; } + + mTimelines.clear(); + mLastCommittedVsync = TimePoint::fromNs(0); + mIdealPeriod = Period::fromNs(idealPeriod()); + mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt); } bool VSyncPredictor::needsMoreSamples() const { @@ -547,6 +553,130 @@ void VSyncPredictor::dump(std::string& result) const { period / 1e6f, periodInterceptTuple.slope / 1e6f, periodInterceptTuple.intercept); } + StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size()); +} + +void VSyncPredictor::purgeTimelines(android::TimePoint now) { + while (mTimelines.size() > 1) { + const auto validUntilOpt = mTimelines.front().validUntil(); + if (validUntilOpt && *validUntilOpt < now) { + mTimelines.pop_front(); + } else { + break; + } + } + LOG_ALWAYS_FATAL_IF(mTimelines.empty()); + LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value()); +} + +VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional renderRateOpt) + : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {} + +void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) { + LOG_ALWAYS_FATAL_IF(mValidUntil.has_value()); + ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f", + mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA", + float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f); + mValidUntil = lastVsync; +} + +std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom( + Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync, + std::optional lastVsyncOpt) { + ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA"); + + const auto threshold = model.slope / 2; + const auto lastFrameMissed = + lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; + nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); + nsecs_t vsyncFixupTime = 0; + if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { + vsyncTime += missedVsync.fixup.ns(); + ATRACE_FORMAT_INSTANT("lastFrameMissed"); + } else { + vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt); + vsyncTime += vsyncFixupTime; + } + + ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f); + if (mValidUntil && vsyncTime > mValidUntil->ns()) { + ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f); + return std::nullopt; + } + + if (vsyncFixupTime > 0) { + shiftVsyncSequence(Duration::fromNs(vsyncFixupTime)); + } + + return TimePoint::fromNs(vsyncTime); +} + +auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync) + -> VsyncSequence { + if (!mLastVsyncSequence) return {vsync, 0}; + + const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; + const auto vsyncSequence = lastVsyncSequence + + static_cast(std::round((vsync - lastVsyncTime) / + static_cast(model.slope))); + return {vsync, vsyncSequence}; +} + +nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model, + nsecs_t vsync) { + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(model, vsync); + + const auto renderRatePhase = [&]() -> int { + if (!mRenderRateOpt) return 0; + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()), + *mRenderRateOpt); + if (divisor <= 1) return 0; + + int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + // This is actually a bug fix, but guarded with vrr_config since we found it with this + // config + if (FlagManager::getInstance().vrr_config()) { + if (mod < 0) mod += divisor; + } + + return divisor - mod; + }(); + + if (renderRatePhase == 0) { + return mLastVsyncSequence->vsyncTime; + } + + return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase; +} + +bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) { + const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float { + return ticks(TimePoint::fromNs(timePoint) - now); + }; + + Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns()); + const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate); + const auto now = TimePoint::now(); + + if (divisor <= 1) { + return true; + } + const auto vsyncSequence = getVsyncSequenceLocked(model, vsync); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu", + getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor); + return vsyncSequence.seq % divisor == 0; +} + +void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) { + if (mLastVsyncSequence) { + ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast(phase.ns()) / 1e6f); + mLastVsyncSequence->vsyncTime += phase.ns(); + } } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 8fd7e6046d..c175765485 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -22,6 +22,7 @@ #include #include +#include #include #include "VSyncTracker.h" @@ -31,6 +32,7 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* + * \param [in] Clock The clock abstraction. Useful for unit tests. * \param [in] PhysicalDisplayid The display this corresponds to. * \param [in] modePtr The initial display mode * \param [in] historySize The internal amount of entries to store in the model. @@ -38,13 +40,13 @@ public: * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(ftl::NonNull modePtr, size_t historySize, + VSyncPredictor(std::unique_ptr, ftl::NonNull modePtr, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt = {}) const final + std::optional lastVsyncOpt = {}) final EXCLUDES(mMutex); nsecs_t currentPeriod() const final EXCLUDES(mMutex); Period minFramePeriod() const final EXCLUDES(mMutex); @@ -62,7 +64,7 @@ public: VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex); - bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); + bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex); void setDisplayModePtr(ftl::NonNull) final EXCLUDES(mMutex); @@ -75,10 +77,42 @@ public: void dump(std::string& result) const final EXCLUDES(mMutex); private: + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + + struct MissedVsync { + TimePoint vsync; + Duration fixup = Duration::fromNs(0); + }; + + class VsyncTimeline { + public: + VsyncTimeline(Period idealPeriod, std::optional renderRateOpt); + std::optional nextAnticipatedVSyncTimeFrom( + Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, + std::optional lastVsyncOpt = {}); + void freeze(TimePoint lastVsync); + std::optional validUntil() const { return mValidUntil; } + bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate); + void shiftVsyncSequence(Duration phase); + + private: + nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); + VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync); + + const Period mIdealPeriod = Duration::fromNs(0); + const std::optional mRenderRateOpt; + std::optional mValidUntil; + std::optional mLastVsyncSequence; + }; + VSyncPredictor(VSyncPredictor const&) = delete; VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); + const std::unique_ptr mClock; const PhysicalDisplayId mId; inline void traceInt64If(const char* name, int64_t value) const; @@ -88,16 +122,10 @@ private: bool validate(nsecs_t timestamp) const REQUIRES(mMutex); Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex); - nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex); - bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); Period minFramePeriodLocked() const REQUIRES(mMutex); - void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); + Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); + void purgeTimelines(android::TimePoint now) REQUIRES(mMutex); - struct VsyncSequence { - nsecs_t vsyncTime; - int64_t seq; - }; - VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); nsecs_t idealPeriod() const REQUIRES(mMutex); bool const mTraceOn; @@ -115,13 +143,15 @@ private: std::vector mTimestamps GUARDED_BY(mMutex); ftl::NonNull mDisplayModePtr GUARDED_BY(mMutex); - std::optional mRenderRateOpt GUARDED_BY(mMutex); - - mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); - TimePoint mLastMissedVsync GUARDED_BY(mMutex); + MissedVsync mMissedVsync GUARDED_BY(mMutex); + + std::deque mTimelines GUARDED_BY(mMutex); + TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0); + Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0); + std::optional mRenderRateOpt GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 37bd4b4977..1e55a87254 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -56,8 +56,8 @@ public: * and avoid crossing the minimal frame period of a VRR display. * \return A prediction of the timestamp of a vsync event. */ - virtual nsecs_t nextAnticipatedVSyncTimeFrom( - nsecs_t timePoint, std::optional lastVsyncOpt = {}) const = 0; + virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, + std::optional lastVsyncOpt = {}) = 0; /* * The current period of the vsync signal. @@ -82,7 +82,7 @@ public: * \param [in] timePoint A vsync timestamp * \param [in] frameRate The frame rate to check for */ - virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; + virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0; /* * Sets the active mode of the display which includes the vsync period and other VRR attributes. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 001938c756..2fa3318560 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -120,8 +120,8 @@ VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull(modePtr, kHistorySize, kMinSamplesForPrediction, - kDiscardOutlierPercent); + return std::make_unique(std::make_unique(), modePtr, kHistorySize, + kMinSamplesForPrediction, kDiscardOutlierPercent); } VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 85cd3e7c31..881d6789b2 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -81,7 +81,7 @@ public: bool addResyncSample(TimePoint timestamp, ftl::Optional hwcVsyncPeriod); // TODO(b/185535769): Hide behind API. - const VsyncTracker& getTracker() const { return *mTracker; } + VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 57839c6135..040bf55e0c 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4700,7 +4700,14 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin return TransactionReadiness::NotReady; } - if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { + const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId}; + + // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued + // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again + // when applying the transaction. Otherwise we might throttle older transactions + // incorrectly as the frame rate of SF changed before it drained the older transactions. + if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID && + !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime, transaction.originUid); return TransactionReadiness::NotReady; @@ -4708,8 +4715,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin // If the client didn't specify desiredPresentTime, use the vsyncId to determine the // expected present time of this transaction. - if (transaction.isAutoTimestamp && - frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { + if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) { ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64, transaction.frameTimelineInfo.vsyncId, expectedPresentTime); return TransactionReadiness::NotReady; diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 10e2220ece..049b0923a6 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -25,6 +25,7 @@ #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncPredictor.h" +#include "Scheduler/VSyncReactor.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockDisplayMode.h" @@ -563,7 +564,8 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { hal::VrrConfig{.minFrameIntervalNs = static_cast( frameRate.getPeriodNsecs())})); std::shared_ptr vrrTracker = - std::make_shared(kMode, kHistorySize, kMinimumSamplesForPrediction, + std::make_shared(std::make_unique(), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr vrrSelectorPtr = std::make_shared(makeModes(kMode), kMode->getId()); @@ -578,6 +580,8 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); vrrTracker->addVsyncTimestamp(0); + // Set 1000 as vsync seq #0 + vrrTracker->nextAnticipatedVSyncTimeFrom(700); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), @@ -587,7 +591,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { TimePoint::fromNs(2000))); // Not crossing the min frame period - EXPECT_EQ(Fps::fromPeriodNsecs(1500), + EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2500))); // Change render rate @@ -595,6 +599,9 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); + // Set 2000 as vsync seq #0 + vrrTracker->nextAnticipatedVSyncTimeFrom(1700); + EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2000))); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index d891008683..c22deaba72 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -48,7 +48,7 @@ public: Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); } void resetModel() final {} bool needsMoreSamples() const final { return false; } - bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } + bool isVSyncInPhase(nsecs_t, Fps) final { return false; } void setDisplayModePtr(ftl::NonNull) final {} void setRenderRate(Fps) final {} void onFrameBegin(TimePoint, TimePoint) final {} @@ -64,7 +64,7 @@ class FixedRateIdealStubTracker : public StubTracker { public: FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) final { auto const floor = timePoint % mPeriod; if (floor == 0) { return timePoint; @@ -77,7 +77,7 @@ class VRRStubTracker : public StubTracker { public: VRRStubTracker(nsecs_t period) : StubTracker(period) {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) const final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) final { std::lock_guard lock(mMutex); auto const normalized_to_base = time_point - mBase; auto const floor = (normalized_to_base) % mPeriod; diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index b9f3d70c6b..6b9ea56062 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -75,6 +75,28 @@ ftl::NonNull displayMode(nsecs_t period) { return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution, DEFAULT_DISPLAY_ID)); } + +class TestClock : public Clock { +public: + TestClock() = default; + + nsecs_t now() const override { return mNow; } + void setNow(nsecs_t now) { mNow = now; } + +private: + nsecs_t mNow = 0; +}; + +class ClockWrapper : public Clock { +public: + ClockWrapper(std::shared_ptr const& clock) : mClock(clock) {} + + nsecs_t now() const { return mClock->now(); } + +private: + std::shared_ptr const mClock; +}; + } // namespace struct VSyncPredictorTest : testing::Test { @@ -86,8 +108,10 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent}; + std::shared_ptr mClock{std::make_shared()}; + + VSyncPredictor tracker{std::make_unique(mClock), mMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; }; TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) { @@ -408,7 +432,8 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { const auto mode = displayMode(mPeriod); - VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{std::make_unique(mClock), mode, 20, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, @@ -606,35 +631,6 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } -TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { - auto last = mNow; - for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); - mNow += mPeriod; - last = mNow; - tracker.addVsyncTimestamp(mNow); - } - - const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); - - tracker.setRenderRate(refreshRate / 4); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); - - tracker.setRenderRate(refreshRate / 2); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); - - tracker.setRenderRate(refreshRate / 6); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); -} - TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { @@ -670,8 +666,8 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { .setVrrConfig(std::move(vrrConfig)) .build()); - VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction, - kOutlierTolerancePercent}; + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate); vrrTracker.addVsyncTimestamp(0); @@ -687,7 +683,44 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { vrrTracker.onFrameMissed(TimePoint::fromNs(4500)); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + + vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500)); + EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000)); +} + +TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { + tracker.addVsyncTimestamp(1000); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); + + tracker.setRenderRate(Fps::fromPeriodNsecs(2000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); + + tracker.setRenderRate(Fps::fromPeriodNsecs(3000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000)); + + // Check the purge logic works + mClock->setNow(20000); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000)); } + } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 3870983133..6d10a5cc5d 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -29,12 +29,12 @@ public: MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override)); MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional), - (const, override)); + (override)); MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override)); MOCK_METHOD(Period, minFramePeriod, (), (const, override)); MOCK_METHOD(void, resetModel, (), (override)); MOCK_METHOD(bool, needsMoreSamples, (), (const, override)); - MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override)); + MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override)); MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull), (override)); MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); -- GitLab From 9f9d0e1ebf16cc8de6633dacf85aecfeb55f9d8d Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Thu, 29 Feb 2024 22:07:12 +0000 Subject: [PATCH 029/465] Revert^2 " Remove TransferDeathRecipients when ABpBinder is deleted" This reverts commit 00aa96b948c9db7cd4a7ea3aca9071272ebd2c9f. Reason for revert: fix multi threading issue The mDeathRecipients emplace_back was not protected and was causing the issues found in b/326585851 Ignore-AOSP-First: b/319210610 Test: atest libbinder_ndk_unit_test Bug: 325612851 Change-Id: I740d89dcac2b814e1796bbfa2e0618e0aac9804d --- libs/binder/ndk/ibinder.cpp | 39 +++++- libs/binder/ndk/ibinder_internal.h | 13 ++ libs/binder/ndk/tests/iface.cpp | 1 + libs/binder/ndk/tests/include/iface/iface.h | 1 + .../ndk/tests/libbinder_ndk_unit_test.cpp | 124 +++++++++++++++++- 5 files changed, 174 insertions(+), 4 deletions(-) diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp index bf7a0ba5f0..e2ede3f52a 100644 --- a/libs/binder/ndk/ibinder.cpp +++ b/libs/binder/ndk/ibinder.cpp @@ -258,11 +258,24 @@ status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parce } } +void ABBinder::addDeathRecipient(const ::android::sp& /* recipient */, + void* /* cookie */) { + LOG_ALWAYS_FATAL("Should not reach this. Can't linkToDeath local binders."); +} + ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder) : AIBinder(nullptr /*clazz*/), mRemote(binder) { LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr"); } -ABpBinder::~ABpBinder() {} + +ABpBinder::~ABpBinder() { + for (auto& recip : mDeathRecipients) { + sp strongRecip = recip.recipient.promote(); + if (strongRecip) { + strongRecip->pruneThisTransferEntry(getBinder(), recip.cookie); + } + } +} sp ABpBinder::lookupOrCreateFromBinder(const ::android::sp<::android::IBinder>& binder) { if (binder == nullptr) { @@ -301,6 +314,12 @@ sp ABpBinder::lookupOrCreateFromBinder(const ::android::sp<::android:: return ret; } +void ABpBinder::addDeathRecipient(const ::android::sp& recipient, + void* cookie) { + std::lock_guard l(mDeathRecipientsMutex); + mDeathRecipients.emplace_back(recipient, cookie); +} + struct AIBinder_Weak { wp binder; }; @@ -426,6 +445,17 @@ AIBinder_DeathRecipient::AIBinder_DeathRecipient(AIBinder_DeathRecipient_onBinde LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr"); } +void AIBinder_DeathRecipient::pruneThisTransferEntry(const sp& who, void* cookie) { + std::lock_guard l(mDeathRecipientsMutex); + mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(), + [&](const sp& tdr) { + auto tdrWho = tdr->getWho(); + return tdrWho != nullptr && tdrWho.promote() == who && + cookie == tdr->getCookie(); + }), + mDeathRecipients.end()); +} + void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() { mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(), [](const sp& tdr) { @@ -554,8 +584,11 @@ binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient* return STATUS_UNEXPECTED_NULL; } - // returns binder_status_t - return recipient->linkToDeath(binder->getBinder(), cookie); + binder_status_t ret = recipient->linkToDeath(binder->getBinder(), cookie); + if (ret == STATUS_OK) { + binder->addDeathRecipient(recipient, cookie); + } + return ret; } binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient, diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h index 9d5368f674..f5b738c1ef 100644 --- a/libs/binder/ndk/ibinder_internal.h +++ b/libs/binder/ndk/ibinder_internal.h @@ -51,6 +51,8 @@ struct AIBinder : public virtual ::android::RefBase { ::android::sp<::android::IBinder> binder = const_cast(this)->getBinder(); return binder->remoteBinder() != nullptr; } + virtual void addDeathRecipient(const ::android::sp& recipient, + void* cookie) = 0; private: // AIBinder instance is instance of this class for a local object. In order to transact on a @@ -78,6 +80,8 @@ struct ABBinder : public AIBinder, public ::android::BBinder { ::android::status_t dump(int fd, const ::android::Vector<::android::String16>& args) override; ::android::status_t onTransact(uint32_t code, const ::android::Parcel& data, ::android::Parcel* reply, binder_flags_t flags) override; + void addDeathRecipient(const ::android::sp& /* recipient */, + void* /* cookie */) override; private: ABBinder(const AIBinder_Class* clazz, void* userData); @@ -106,12 +110,20 @@ struct ABpBinder : public AIBinder { bool isServiceFuzzing() const { return mServiceFuzzing; } void setServiceFuzzing() { mServiceFuzzing = true; } + void addDeathRecipient(const ::android::sp& recipient, + void* cookie) override; private: friend android::sp; explicit ABpBinder(const ::android::sp<::android::IBinder>& binder); ::android::sp<::android::IBinder> mRemote; bool mServiceFuzzing = false; + struct DeathRecipientInfo { + android::wp recipient; + void* cookie; + }; + std::mutex mDeathRecipientsMutex; + std::vector mDeathRecipients; }; struct AIBinder_Class { @@ -183,6 +195,7 @@ struct AIBinder_DeathRecipient : ::android::RefBase { binder_status_t linkToDeath(const ::android::sp<::android::IBinder>&, void* cookie); binder_status_t unlinkToDeath(const ::android::sp<::android::IBinder>& binder, void* cookie); void setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked); + void pruneThisTransferEntry(const ::android::sp<::android::IBinder>&, void* cookie); private: // When the user of this API deletes a Bp object but not the death recipient, the diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp index 3ee36cd8c3..ca927272f8 100644 --- a/libs/binder/ndk/tests/iface.cpp +++ b/libs/binder/ndk/tests/iface.cpp @@ -25,6 +25,7 @@ using ::android::wp; const char* IFoo::kSomeInstanceName = "libbinder_ndk-test-IFoo"; const char* IFoo::kInstanceNameToDieFor = "libbinder_ndk-test-IFoo-to-die"; +const char* IFoo::kInstanceNameToDieFor2 = "libbinder_ndk-test-IFoo-to-die2"; const char* IFoo::kIFooDescriptor = "my-special-IFoo-class"; struct IFoo_Class_Data { diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h index 0a562f085d..0cdd50b37a 100644 --- a/libs/binder/ndk/tests/include/iface/iface.h +++ b/libs/binder/ndk/tests/include/iface/iface.h @@ -27,6 +27,7 @@ class IFoo : public virtual ::android::RefBase { public: static const char* kSomeInstanceName; static const char* kInstanceNameToDieFor; + static const char* kInstanceNameToDieFor2; static const char* kIFooDescriptor; static AIBinder_Class* kClass; diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp index 966ec959b6..ce63b828cb 100644 --- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp +++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp @@ -536,6 +536,7 @@ TEST(NdkBinder, DeathRecipient) { bool deathReceived = false; std::function onDeath = [&] { + std::unique_lock lockDeath(deathMutex); std::cerr << "Binder died (as requested)." << std::endl; deathReceived = true; deathCv.notify_one(); @@ -547,6 +548,7 @@ TEST(NdkBinder, DeathRecipient) { bool wasDeathReceivedFirst = false; std::function onUnlink = [&] { + std::unique_lock lockUnlink(unlinkMutex); std::cerr << "Binder unlinked (as requested)." << std::endl; wasDeathReceivedFirst = deathReceived; unlinkReceived = true; @@ -560,7 +562,6 @@ TEST(NdkBinder, DeathRecipient) { EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast(cookie))); - // the binder driver should return this if the service dies during the transaction EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die()); foo = nullptr; @@ -579,6 +580,123 @@ TEST(NdkBinder, DeathRecipient) { binder = nullptr; } +TEST(NdkBinder, DeathRecipientDropBinderNoDeath) { + using namespace std::chrono_literals; + + std::mutex deathMutex; + std::condition_variable deathCv; + bool deathReceived = false; + + std::function onDeath = [&] { + std::unique_lock lockDeath(deathMutex); + std::cerr << "Binder died (as requested)." << std::endl; + deathReceived = true; + deathCv.notify_one(); + }; + + std::mutex unlinkMutex; + std::condition_variable unlinkCv; + bool unlinkReceived = false; + bool wasDeathReceivedFirst = false; + + std::function onUnlink = [&] { + std::unique_lock lockUnlink(unlinkMutex); + std::cerr << "Binder unlinked (as requested)." << std::endl; + wasDeathReceivedFirst = deathReceived; + unlinkReceived = true; + unlinkCv.notify_one(); + }; + + // keep the death recipient around + ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath)); + AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink); + + { + AIBinder* binder; + sp foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder); + ASSERT_NE(nullptr, foo.get()); + ASSERT_NE(nullptr, binder); + + DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink}; + + EXPECT_EQ(STATUS_OK, + AIBinder_linkToDeath(binder, recipient.get(), static_cast(cookie))); + // let the sp and AIBinder fall out of scope + AIBinder_decStrong(binder); + binder = nullptr; + } + + { + std::unique_lock lockDeath(deathMutex); + EXPECT_FALSE(deathCv.wait_for(lockDeath, 100ms, [&] { return deathReceived; })); + EXPECT_FALSE(deathReceived); + } + + { + std::unique_lock lockUnlink(unlinkMutex); + EXPECT_TRUE(deathCv.wait_for(lockUnlink, 1s, [&] { return unlinkReceived; })); + EXPECT_TRUE(unlinkReceived); + EXPECT_FALSE(wasDeathReceivedFirst); + } +} + +TEST(NdkBinder, DeathRecipientDropBinderOnDied) { + using namespace std::chrono_literals; + + std::mutex deathMutex; + std::condition_variable deathCv; + bool deathReceived = false; + + sp foo; + AIBinder* binder; + std::function onDeath = [&] { + std::unique_lock lockDeath(deathMutex); + std::cerr << "Binder died (as requested)." << std::endl; + deathReceived = true; + AIBinder_decStrong(binder); + binder = nullptr; + deathCv.notify_one(); + }; + + std::mutex unlinkMutex; + std::condition_variable unlinkCv; + bool unlinkReceived = false; + bool wasDeathReceivedFirst = false; + + std::function onUnlink = [&] { + std::unique_lock lockUnlink(unlinkMutex); + std::cerr << "Binder unlinked (as requested)." << std::endl; + wasDeathReceivedFirst = deathReceived; + unlinkReceived = true; + unlinkCv.notify_one(); + }; + + ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath)); + AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink); + + foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder); + ASSERT_NE(nullptr, foo.get()); + ASSERT_NE(nullptr, binder); + + DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink}; + EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient.get(), static_cast(cookie))); + + EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die()); + + { + std::unique_lock lockDeath(deathMutex); + EXPECT_TRUE(deathCv.wait_for(lockDeath, 1s, [&] { return deathReceived; })); + EXPECT_TRUE(deathReceived); + } + + { + std::unique_lock lockUnlink(unlinkMutex); + EXPECT_TRUE(deathCv.wait_for(lockUnlink, 100ms, [&] { return unlinkReceived; })); + EXPECT_TRUE(unlinkReceived); + EXPECT_TRUE(wasDeathReceivedFirst); + } +} + TEST(NdkBinder, RetrieveNonNdkService) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -956,6 +1074,10 @@ int main(int argc, char* argv[]) { prctl(PR_SET_PDEATHSIG, SIGHUP); return manualThreadPoolService(IFoo::kInstanceNameToDieFor); } + if (fork() == 0) { + prctl(PR_SET_PDEATHSIG, SIGHUP); + return manualThreadPoolService(IFoo::kInstanceNameToDieFor2); + } if (fork() == 0) { prctl(PR_SET_PDEATHSIG, SIGHUP); return manualPollingService(IFoo::kSomeInstanceName); -- GitLab From 9a6cfce7195c67112b32f191ee0178999c24fb70 Mon Sep 17 00:00:00 2001 From: ramindani Date: Tue, 5 Mar 2024 13:00:26 -0800 Subject: [PATCH 030/465] [SF] Change FrameRateCategory Max range to 120 When we cap the refresh rate we don't select the higher refresh rate for the cases when multiple layers vote for the lower rate. This affects the frames which request the higher rate along side the frames that vote for lower rate. This change will give the higher refresh rate an equal amount of weight in this case. Test: atest RefreshRateSelectorTest Bug: 328084623 Change-Id: I8a48e2b066e8908ff8f7579615260d8c74a79bff --- .../Scheduler/RefreshRateSelector.cpp | 6 +- .../unittests/RefreshRateSelectorTest.cpp | 132 +++++++++++++++++- 2 files changed, 132 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index ffd3463296..3e1d8b5bf3 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -1554,19 +1554,17 @@ FpsRange RefreshRateSelector::getFrameRateCategoryRange(FrameRateCategory catego case FrameRateCategory::High: return FpsRange{90_Hz, 120_Hz}; case FrameRateCategory::Normal: - return FpsRange{60_Hz, 90_Hz}; + return FpsRange{60_Hz, 120_Hz}; case FrameRateCategory::Low: - return FpsRange{30_Hz, 30_Hz}; + return FpsRange{30_Hz, 120_Hz}; case FrameRateCategory::HighHint: case FrameRateCategory::NoPreference: case FrameRateCategory::Default: LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s", ftl::enum_string(category).c_str()); - return FpsRange{0_Hz, 0_Hz}; default: LOG_ALWAYS_FATAL("Invalid frame rate category for range: %s", ftl::enum_string(category).c_str()); - return FpsRange{0_Hz, 0_Hz}; } } diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 0a6e3054dd..d5b6852c1a 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -259,6 +259,44 @@ protected: config.enableFrameRateOverride = GetParam(); return TestableRefreshRateSelector(modes, activeModeId, config); } + + template + void testFrameRateCategoryWithMultipleLayers(const std::initializer_list& testCases, + const TestableRefreshRateSelector& selector) { + std::vector layers; + for (auto testCase : testCases) { + ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__, + to_string(testCase.desiredFrameRate).c_str(), + ftl::enum_string(testCase.frameRateCategory).c_str()); + + if (testCase.desiredFrameRate.isValid()) { + std::stringstream ss; + ss << to_string(testCase.desiredFrameRate) + << ftl::enum_string(testCase.frameRateCategory) << "ExplicitDefault"; + LayerRequirement layer = {.name = ss.str(), + .vote = LayerVoteType::ExplicitDefault, + .desiredRefreshRate = testCase.desiredFrameRate, + .weight = 1.f}; + layers.push_back(layer); + } + + if (testCase.frameRateCategory != FrameRateCategory::Default) { + std::stringstream ss; + ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")"; + LayerRequirement layer = {.name = ss.str(), + .vote = LayerVoteType::ExplicitCategory, + .frameRateCategory = testCase.frameRateCategory, + .weight = 1.f}; + layers.push_back(layer); + } + + EXPECT_EQ(testCase.expectedFrameRate, + selector.getBestFrameRateMode(layers).modePtr->getPeakFps()) + << "Did not get expected frame rate for frameRate=" + << to_string(testCase.desiredFrameRate) + << " category=" << ftl::enum_string(testCase.frameRateCategory); + } + } }; RefreshRateSelectorTest::RefreshRateSelectorTest() { @@ -1542,6 +1580,96 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_30_60 } } +TEST_P(RefreshRateSelectorTest, + getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) { + auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60); + + struct Case { + // Params + Fps desiredFrameRate = 0_Hz; + FrameRateCategory frameRateCategory = FrameRateCategory::Default; + + // Expected result + Fps expectedFrameRate = 0_Hz; + }; + + testFrameRateCategoryWithMultipleLayers( + std::initializer_list{ + {0_Hz, FrameRateCategory::High, 90_Hz}, + {0_Hz, FrameRateCategory::NoPreference, 90_Hz}, + {0_Hz, FrameRateCategory::Normal, 90_Hz}, + {0_Hz, FrameRateCategory::Normal, 90_Hz}, + {0_Hz, FrameRateCategory::NoPreference, 90_Hz}, + }, + selector); + + testFrameRateCategoryWithMultipleLayers( + std::initializer_list{ + {0_Hz, FrameRateCategory::Normal, 60_Hz}, + {0_Hz, FrameRateCategory::High, 90_Hz}, + {0_Hz, FrameRateCategory::NoPreference, 90_Hz}, + }, + selector); + + testFrameRateCategoryWithMultipleLayers( + std::initializer_list{ + {30_Hz, FrameRateCategory::High, 90_Hz}, + {24_Hz, FrameRateCategory::High, 120_Hz}, + {12_Hz, FrameRateCategory::Normal, 120_Hz}, + {30_Hz, FrameRateCategory::NoPreference, 120_Hz}, + + }, + selector); + + testFrameRateCategoryWithMultipleLayers( + std::initializer_list{ + {24_Hz, FrameRateCategory::Default, 120_Hz}, + {30_Hz, FrameRateCategory::Default, 120_Hz}, + {120_Hz, FrameRateCategory::Default, 120_Hz}, + }, + selector); +} + +TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategoryMultiLayers_60_120) { + auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60); + + struct Case { + // Params + Fps desiredFrameRate = 0_Hz; + FrameRateCategory frameRateCategory = FrameRateCategory::Default; + + // Expected result + Fps expectedFrameRate = 0_Hz; + }; + + testFrameRateCategoryWithMultipleLayers(std::initializer_list< + Case>{{0_Hz, FrameRateCategory::High, 120_Hz}, + {0_Hz, FrameRateCategory::NoPreference, + 120_Hz}, + {0_Hz, FrameRateCategory::Normal, 120_Hz}, + {0_Hz, FrameRateCategory::Normal, 120_Hz}, + {0_Hz, FrameRateCategory::NoPreference, + 120_Hz}}, + selector); + + testFrameRateCategoryWithMultipleLayers(std::initializer_list< + Case>{{24_Hz, FrameRateCategory::High, 120_Hz}, + {30_Hz, FrameRateCategory::High, 120_Hz}, + {12_Hz, FrameRateCategory::Normal, + 120_Hz}, + {30_Hz, FrameRateCategory::NoPreference, + 120_Hz}}, + selector); + + testFrameRateCategoryWithMultipleLayers( + std::initializer_list{ + {24_Hz, FrameRateCategory::Default, 120_Hz}, + {30_Hz, FrameRateCategory::Default, 120_Hz}, + {120_Hz, FrameRateCategory::Default, 120_Hz}, + }, + selector); +} + TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) { auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60); @@ -1725,8 +1853,8 @@ TEST_P(RefreshRateSelectorTest, // These layers cannot change mode due to smoothSwitchOnly, and will definitely use // active mode (120Hz). {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120}, - {FrameRateCategory::Low, true, 120_Hz, kModeId120}, - {FrameRateCategory::Normal, true, 40_Hz, kModeId120}, + {FrameRateCategory::Low, true, 40_Hz, kModeId120}, + {FrameRateCategory::Normal, true, 120_Hz, kModeId120}, {FrameRateCategory::High, true, 120_Hz, kModeId120}, }; -- GitLab From 9dbf63750fcc389713237ff5f4df2c3fac40826a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 6 Mar 2024 16:08:01 -0800 Subject: [PATCH 031/465] Move static functions to InputConsumer cpp file This way, they can be reused by other code in that file. For example, by the new "InputConsumerNoResampling". Also, pass the parameters by ref because they are not nullable. Bug: 311142655 Test: m libinput libinput_tests Change-Id: Ib40bcddfcb3d49b0a42d58533dcc566d9d49a500 --- include/input/InputTransport.h | 7 -- libs/input/InputTransport.cpp | 145 ++++++++++++++++----------------- 2 files changed, 72 insertions(+), 80 deletions(-) diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 42dcd3c394..aca4b622d1 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -670,13 +670,6 @@ private: status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled); static void rewriteMessage(TouchState& state, InputMessage& msg); - static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg); - static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg); - static void initializeFocusEvent(FocusEvent* event, const InputMessage* msg); - static void initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg); - static void initializeDragEvent(DragEvent* event, const InputMessage* msg); - static void initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg); - static void addSample(MotionEvent* event, const InputMessage* msg); static bool canAddSample(const Batch& batch, const InputMessage* msg); static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time); diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index e49f4eb6f6..f9fa420ea9 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -30,6 +30,8 @@ namespace input_flags = com::android::input::flags; +namespace android { + namespace { /** @@ -110,13 +112,73 @@ android::base::unique_fd dupChannelFd(int fd) { return newFd; } +void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) { + event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, + msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, + msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, + msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, + msg.body.key.eventTime); +} + +void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) { + event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus); +} + +void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) { + event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled); +} + +void initializeDragEvent(DragEvent& event, const InputMessage& msg) { + event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y, + msg.body.drag.isExiting); +} + +void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { + uint32_t pointerCount = msg.body.motion.pointerCount; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + for (uint32_t i = 0; i < pointerCount; i++) { + pointerProperties[i] = msg.body.motion.pointers[i].properties; + pointerCoords[i] = msg.body.motion.pointers[i].coords; + } + + ui::Transform transform; + transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx, + msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1}); + ui::Transform displayTransform; + displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw, + msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, + 0, 0, 1}); + event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, + msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, + msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, + msg.body.motion.metaState, msg.body.motion.buttonState, + msg.body.motion.classification, transform, msg.body.motion.xPrecision, + msg.body.motion.yPrecision, msg.body.motion.xCursorPosition, + msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime, + msg.body.motion.eventTime, pointerCount, pointerProperties, pointerCoords); +} + +void addSample(MotionEvent& event, const InputMessage& msg) { + uint32_t pointerCount = msg.body.motion.pointerCount; + PointerCoords pointerCoords[pointerCount]; + for (uint32_t i = 0; i < pointerCount; i++) { + pointerCoords[i] = msg.body.motion.pointers[i].coords; + } + + event.setMetaState(event.getMetaState() | msg.body.motion.metaState); + event.addSample(msg.body.motion.eventTime, pointerCoords); +} + +void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) { + event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); +} + } // namespace using android::base::Result; using android::base::StringPrintf; -namespace android { - // Socket buffer size. The default is typically about 128KB, which is much larger than // we really need. So we make it smaller. It just needs to be big enough to hold // a few dozen large multi-finger motion events in the case where an application gets @@ -902,7 +964,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum KeyEvent* keyEvent = factory->createKeyEvent(); if (!keyEvent) return NO_MEMORY; - initializeKeyEvent(keyEvent, &mMsg); + initializeKeyEvent(*keyEvent, mMsg); *outSeq = mMsg.header.seq; *outEvent = keyEvent; ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, @@ -965,7 +1027,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum if (!motionEvent) return NO_MEMORY; updateTouchState(mMsg); - initializeMotionEvent(motionEvent, &mMsg); + initializeMotionEvent(*motionEvent, mMsg); *outSeq = mMsg.header.seq; *outEvent = motionEvent; @@ -987,7 +1049,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum FocusEvent* focusEvent = factory->createFocusEvent(); if (!focusEvent) return NO_MEMORY; - initializeFocusEvent(focusEvent, &mMsg); + initializeFocusEvent(*focusEvent, mMsg); *outSeq = mMsg.header.seq; *outEvent = focusEvent; break; @@ -997,7 +1059,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum CaptureEvent* captureEvent = factory->createCaptureEvent(); if (!captureEvent) return NO_MEMORY; - initializeCaptureEvent(captureEvent, &mMsg); + initializeCaptureEvent(*captureEvent, mMsg); *outSeq = mMsg.header.seq; *outEvent = captureEvent; break; @@ -1007,7 +1069,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum DragEvent* dragEvent = factory->createDragEvent(); if (!dragEvent) return NO_MEMORY; - initializeDragEvent(dragEvent, &mMsg); + initializeDragEvent(*dragEvent, mMsg); *outSeq = mMsg.header.seq; *outEvent = dragEvent; break; @@ -1017,7 +1079,7 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum TouchModeEvent* touchModeEvent = factory->createTouchModeEvent(); if (!touchModeEvent) return NO_MEMORY; - initializeTouchModeEvent(touchModeEvent, &mMsg); + initializeTouchModeEvent(*touchModeEvent, mMsg); *outSeq = mMsg.header.seq; *outEvent = touchModeEvent; break; @@ -1079,9 +1141,9 @@ status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, seqChain.seq = msg.header.seq; seqChain.chain = chain; mSeqChains.push_back(seqChain); - addSample(motionEvent, &msg); + addSample(*motionEvent, msg); } else { - initializeMotionEvent(motionEvent, &msg); + initializeMotionEvent(*motionEvent, msg); } chain = msg.header.seq; } @@ -1465,69 +1527,6 @@ ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const { return -1; } -void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) { - event->initialize(msg->body.key.eventId, msg->body.key.deviceId, msg->body.key.source, - msg->body.key.displayId, msg->body.key.hmac, msg->body.key.action, - msg->body.key.flags, msg->body.key.keyCode, msg->body.key.scanCode, - msg->body.key.metaState, msg->body.key.repeatCount, msg->body.key.downTime, - msg->body.key.eventTime); -} - -void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) { - event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus); -} - -void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) { - event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled); -} - -void InputConsumer::initializeDragEvent(DragEvent* event, const InputMessage* msg) { - event->initialize(msg->body.drag.eventId, msg->body.drag.x, msg->body.drag.y, - msg->body.drag.isExiting); -} - -void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) { - uint32_t pointerCount = msg->body.motion.pointerCount; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - for (uint32_t i = 0; i < pointerCount; i++) { - pointerProperties[i] = msg->body.motion.pointers[i].properties; - pointerCoords[i] = msg->body.motion.pointers[i].coords; - } - - ui::Transform transform; - transform.set({msg->body.motion.dsdx, msg->body.motion.dtdx, msg->body.motion.tx, - msg->body.motion.dtdy, msg->body.motion.dsdy, msg->body.motion.ty, 0, 0, 1}); - ui::Transform displayTransform; - displayTransform.set({msg->body.motion.dsdxRaw, msg->body.motion.dtdxRaw, - msg->body.motion.txRaw, msg->body.motion.dtdyRaw, - msg->body.motion.dsdyRaw, msg->body.motion.tyRaw, 0, 0, 1}); - event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source, - msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action, - msg->body.motion.actionButton, msg->body.motion.flags, - msg->body.motion.edgeFlags, msg->body.motion.metaState, - msg->body.motion.buttonState, msg->body.motion.classification, transform, - msg->body.motion.xPrecision, msg->body.motion.yPrecision, - msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition, - displayTransform, msg->body.motion.downTime, msg->body.motion.eventTime, - pointerCount, pointerProperties, pointerCoords); -} - -void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) { - event->initialize(msg->body.touchMode.eventId, msg->body.touchMode.isInTouchMode); -} - -void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) { - uint32_t pointerCount = msg->body.motion.pointerCount; - PointerCoords pointerCoords[pointerCount]; - for (uint32_t i = 0; i < pointerCount; i++) { - pointerCoords[i] = msg->body.motion.pointers[i].coords; - } - - event->setMetaState(event->getMetaState() | msg->body.motion.metaState); - event->addSample(msg->body.motion.eventTime, pointerCoords); -} - bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) { const InputMessage& head = batch.samples[0]; uint32_t pointerCount = msg->body.motion.pointerCount; -- GitLab From 1e6df10a9bcd49245e4fbcd68956dcd4b8af7ad4 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 6 Mar 2024 16:34:39 -0800 Subject: [PATCH 032/465] Delete TestHelpers.h This code is unused. Bug: 311142655 Test: m libinput_tests Change-Id: I335d1da6ec1b4463d3032ea081f8846cbe9393a6 --- libs/input/tests/InputChannel_test.cpp | 2 - .../tests/InputPublisherAndConsumer_test.cpp | 2 - libs/input/tests/TestHelpers.h | 81 ------------------- libs/input/tests/TouchResampling_test.cpp | 2 - 4 files changed, 87 deletions(-) delete mode 100644 libs/input/tests/TestHelpers.h diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp index 60feb53dcc..02d4c07bfa 100644 --- a/libs/input/tests/InputChannel_test.cpp +++ b/libs/input/tests/InputChannel_test.cpp @@ -16,8 +16,6 @@ #include -#include "TestHelpers.h" - #include #include #include diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 35430207f9..f9d0bdaf80 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "TestHelpers.h" - #include #include #include diff --git a/libs/input/tests/TestHelpers.h b/libs/input/tests/TestHelpers.h deleted file mode 100644 index 343d81f917..0000000000 --- a/libs/input/tests/TestHelpers.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#ifndef TESTHELPERS_H -#define TESTHELPERS_H - -#include - -#include - -namespace android { - -class Pipe { -public: - int sendFd; - int receiveFd; - - Pipe() { - int fds[2]; - ::pipe(fds); - - receiveFd = fds[0]; - sendFd = fds[1]; - } - - ~Pipe() { - if (sendFd != -1) { - ::close(sendFd); - } - - if (receiveFd != -1) { - ::close(receiveFd); - } - } - - status_t writeSignal() { - ssize_t nWritten = ::write(sendFd, "*", 1); - return nWritten == 1 ? 0 : -errno; - } - - status_t readSignal() { - char buf[1]; - ssize_t nRead = ::read(receiveFd, buf, 1); - return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno; - } -}; - -class DelayedTask : public Thread { - int mDelayMillis; - -public: - explicit DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { } - -protected: - virtual ~DelayedTask() { } - - virtual void doTask() = 0; - - virtual bool threadLoop() { - usleep(mDelayMillis * 1000); - doTask(); - return false; - } -}; - -} // namespace android - -#endif // TESTHELPERS_H diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 1cb7f7ba8c..0b0bb63a81 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#include "TestHelpers.h" - #include #include -- GitLab From 3fea36d90803810a08bcd127371ef5e71262ca2d Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 6 Mar 2024 21:20:04 +0000 Subject: [PATCH 033/465] FocusResolver: Add test to document focus transfer to mirror Document how we behave when focus is transferred automatically from a window to its mirror. Bug: None Test: atest inputflinger_tests Change-Id: Ie2237b6064989bc83cf58a407b96ac73ab9156a7 --- .../inputflinger/tests/FocusResolver_test.cpp | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp index 2ff9c3c784..cb8c3cb03c 100644 --- a/services/inputflinger/tests/FocusResolver_test.cpp +++ b/services/inputflinger/tests/FocusResolver_test.cpp @@ -20,6 +20,7 @@ #define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \ { \ + ASSERT_TRUE(_changes.has_value()); \ ASSERT_EQ(_oldFocus, _changes->oldFocus); \ ASSERT_EQ(_newFocus, _changes->newFocus); \ } @@ -152,6 +153,38 @@ TEST(FocusResolverTest, SetFocusedMirroredWindow) { ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr); } +TEST(FocusResolverTest, FocusTransferToMirror) { + sp focusableWindowToken = sp::make(); + auto window = sp::make("Window", focusableWindowToken, + /*focusable=*/true, /*visible=*/true); + auto mirror = sp::make("Mirror", focusableWindowToken, + /*focusable=*/true, /*visible=*/true); + + FocusRequest request; + request.displayId = 42; + request.token = focusableWindowToken; + FocusResolver focusResolver; + std::optional changes = + focusResolver.setFocusedWindow(request, {window, mirror}); + ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken); + + // The mirror window now comes on top, and the focus does not change + changes = focusResolver.setInputWindows(request.displayId, {mirror, window}); + ASSERT_FALSE(changes.has_value()); + + // The window now comes on top while the mirror is removed, and the focus does not change + changes = focusResolver.setInputWindows(request.displayId, {window}); + ASSERT_FALSE(changes.has_value()); + + // The window is removed but the mirror is on top, and focus does not change + changes = focusResolver.setInputWindows(request.displayId, {mirror}); + ASSERT_FALSE(changes.has_value()); + + // All windows removed + changes = focusResolver.setInputWindows(request.displayId, {}); + ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr); +} + TEST(FocusResolverTest, SetInputWindows) { sp focusableWindowToken = sp::make(); std::vector> windows; @@ -169,6 +202,10 @@ TEST(FocusResolverTest, SetInputWindows) { focusResolver.setFocusedWindow(request, windows); ASSERT_EQ(focusableWindowToken, changes->newFocus); + // When there are no changes to the window, focus does not change + changes = focusResolver.setInputWindows(request.displayId, windows); + ASSERT_FALSE(changes.has_value()); + // Window visibility changes and the window loses focus window->setVisible(false); changes = focusResolver.setInputWindows(request.displayId, windows); @@ -380,18 +417,13 @@ TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) { ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); ASSERT_EQ(request.displayId, changes->displayId); - // Start with a focused window - window->setFocusable(true); - changes = focusResolver.setInputWindows(request.displayId, windows); - ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); - // When a display is removed, all windows are removed from the display // and our focused window loses focus changes = focusResolver.setInputWindows(request.displayId, {}); ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr); focusResolver.displayRemoved(request.displayId); - // When a display is readded, the window does not get focus since the request was cleared. + // When a display is re-added, the window does not get focus since the request was cleared. changes = focusResolver.setInputWindows(request.displayId, windows); ASSERT_FALSE(changes); } -- GitLab From 560d0d1599df555e69eec23d423cc4b8c4820c4f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 7 Mar 2024 18:08:27 +0000 Subject: [PATCH 034/465] InputTracer: Trace resolved key repeat count during dispatch Since the repeatCount in KeyEntry is mutable, it can change after an event is traced, which is not ideal. Until we change this behavior, we should trace the resolved repeat count during dispatch to account for changed repeat counts. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I86cdb2cbbd77b7bb834b9d8e66176837c113c1ca --- services/inputflinger/dispatcher/Entry.h | 1 + services/inputflinger/dispatcher/trace/InputTracer.cpp | 7 ++++++- .../dispatcher/trace/InputTracingBackendInterface.h | 1 + services/inputflinger/tests/FakeInputTracingBackend.cpp | 4 ++-- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 1298b5d511..06d5c7dd4b 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -140,6 +140,7 @@ struct KeyEntry : EventEntry { mutable InterceptKeyResult interceptKeyResult; // set based on the interception result mutable nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER mutable int32_t flags; + // TODO(b/328618922): Refactor key repeat generation to make repeatCount non-mutable. mutable int32_t repeatCount; KeyEntry(int32_t id, std::shared_ptr injectionState, nsecs_t eventTime, diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 0be64e67ca..10f6b0f768 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -107,6 +107,10 @@ void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, const EventTrackerInterface* cookie) { const EventEntry& entry = *dispatchEntry.eventEntry; + // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable. + // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced, + // so we need to find the repeatCount at the time of dispatching to trace it accurately. + int32_t resolvedKeyRepeatCount = 0; TracedEvent traced; if (entry.type == EventEntry::Type::MOTION) { @@ -114,6 +118,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, traced = createTracedEvent(motion); } else if (entry.type == EventEntry::Type::KEY) { const auto& key = static_cast(entry); + resolvedKeyRepeatCount = key.repeatCount; traced = createTracedEvent(key); } else { LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); @@ -133,7 +138,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, mBackend->traceWindowDispatch({std::move(traced), dispatchEntry.deliveryTime, dispatchEntry.resolvedFlags, dispatchEntry.targetUid, vsyncId, windowId, dispatchEntry.transform, dispatchEntry.rawTransform, - /*hmac=*/{}}); + /*hmac=*/{}, resolvedKeyRepeatCount}); } InputTracer::EventState& InputTracer::getState(const EventTrackerInterface& cookie) { diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index b0eadfe51f..94a86b94a2 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -98,6 +98,7 @@ public: ui::Transform transform; ui::Transform rawTransform; std::array hmac; + int32_t resolvedKeyRepeatCount; }; virtual void traceWindowDispatch(const WindowDispatchArgs&) = 0; }; diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp index 4655ee8458..08738e3973 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.cpp +++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp @@ -56,8 +56,8 @@ KeyEvent toInputEvent(const trace::TracedKeyEvent& e, const std::array& hmac) { KeyEvent traced; traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action, - dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState, e.repeatCount, - e.downTime, e.eventTime); + dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState, + dispatchArgs.resolvedKeyRepeatCount, e.downTime, e.eventTime); return traced; } -- GitLab From fe3c8f189605472c6d9c567be7170c649f162ef6 Mon Sep 17 00:00:00 2001 From: Hu Guo Date: Fri, 22 Sep 2023 17:20:15 +0800 Subject: [PATCH 035/465] Fix incorrect repeatCount sent to app when injecting repeat event When the injected repeat event occurs after the repeat event synthesized by the InputDispatcher, the repeatCount of the event sent to the app is incorrect. For example, the repeatCount sequence of the event received by the app might be 0,1,2,1,2,3,4,.... Test: atest inputflinger_tests Change-Id: I13574a77896583bb0062bab17395d27585315e6b --- .../dispatcher/InputDispatcher.cpp | 2 +- .../tests/InputDispatcher_test.cpp | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 057b996a23..d06881d962 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1780,7 +1780,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrdispatchInProgress) { - if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN && + if (!entry->syntheticRepeat && entry->action == AKEY_EVENT_ACTION_DOWN && (entry->policyFlags & POLICY_FLAG_TRUSTED) && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) { if (mKeyRepeatState.lastKeyEntry && diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index f0f4d93ecd..60d598ff80 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -7477,6 +7477,12 @@ protected: mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); } + + void injectKeyRepeat(int32_t repeatCount) { + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, repeatCount, ADISPLAY_ID_DEFAULT)) + << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; + } }; TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) { @@ -7565,6 +7571,17 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEvent } } +TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_CorrectRepeatCountWhenInjectKeyRepeat) { + injectKeyRepeat(0); + mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + for (int32_t repeatCount = 1; repeatCount <= 2; ++repeatCount) { + expectKeyRepeatOnce(repeatCount); + } + injectKeyRepeat(1); + // Expect repeatCount to be 3 instead of 1 + expectKeyRepeatOnce(3); +} + /* Test InputDispatcher for MultiDisplay */ class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest { public: @@ -8709,9 +8726,10 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { // Define a valid key down event that is stale (too old). event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A, - AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime); + AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); - const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER; + const int32_t policyFlags = + POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT; InputEventInjectionResult result = mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, -- GitLab From 1cb47eb98857cdfe8c7ecf4dc0d5185597c59cf6 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Thu, 7 Mar 2024 11:44:36 -0800 Subject: [PATCH 036/465] Add Telephony Data feature android.hardware.telephony.data Bug: 310710841 Test: CF builds and boot Change-Id: I36523f6cf1092a2917287ec70844d0553eb76ccc --- data/etc/Android.bp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 8b2842a3ae..71516966db 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -286,6 +286,12 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +prebuilt_etc { + name: "android.hardware.telephony.data.prebuilt.xml", + src: "android.hardware.telephony.data.xml", + defaults: ["frameworks_native_data_etc_defaults"], +} + prebuilt_etc { name: "android.hardware.telephony.gsm.prebuilt.xml", src: "android.hardware.telephony.gsm.xml", -- GitLab From 6c1e622e21016c558adc448ff2e937b7ca7ef580 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 7 Mar 2024 13:39:24 -0800 Subject: [PATCH 037/465] InputConsumer: move static functions to anonymous namespace Also, delete some functions that we already have defined elsewhere. Bug: 311142655 Test: m libinput_tests Change-Id: Icdcd871460e020a34a196510a5192604251603bf --- libs/input/InputTransport.cpp | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index f9fa420ea9..b3a36ebf5a 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -26,6 +26,7 @@ #include #include +#include #include namespace input_flags = com::android::input::flags; @@ -174,19 +175,14 @@ void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) { event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); } -} // namespace - -using android::base::Result; -using android::base::StringPrintf; - // Socket buffer size. The default is typically about 128KB, which is much larger than // we really need. So we make it smaller. It just needs to be big enough to hold // a few dozen large multi-finger motion events in the case where an application gets // behind processing touches. -static const size_t SOCKET_BUFFER_SIZE = 32 * 1024; +static constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024; // Nanoseconds per milliseconds. -static const nsecs_t NANOS_PER_MS = 1000000; +static constexpr nsecs_t NANOS_PER_MS = 1000000; // Latency added during resampling. A few milliseconds doesn't hurt much but // reduces the impact of mispredicted touch positions. @@ -219,32 +215,28 @@ static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling"; * Crash if the events that are getting sent to the InputPublisher are inconsistent. * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG" */ -static bool verifyEvents() { +bool verifyEvents() { return input_flags::enable_outbound_event_verification() || __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO); } -template -inline static T min(const T& a, const T& b) { - return a < b ? a : b; -} - -inline static float lerp(float a, float b, float alpha) { +inline float lerp(float a, float b, float alpha) { return a + alpha * (b - a); } -inline static bool isPointerEvent(int32_t source) { +inline bool isPointerEvent(int32_t source) { return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER; } -inline static const char* toString(bool value) { - return value ? "true" : "false"; -} - -static bool shouldResampleTool(ToolType toolType) { +bool shouldResampleTool(ToolType toolType) { return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN; } +} // namespace + +using android::base::Result; +using android::base::StringPrintf; + // --- InputMessage --- bool InputMessage::isValid(size_t actualSize) const { @@ -1324,7 +1316,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, delta); return; } - nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION); + nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION); if (sampleTime > maxPredict) { ALOGD_IF(debugResampling(), "Sample time is too far in the future, adjusting prediction " -- GitLab From e54ce10b5ca326d97bc55572cb3930e87db001d4 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 4 Mar 2024 23:18:38 +0000 Subject: [PATCH 038/465] SF: fix vsync shift repeatedly when HWC miss a frame Bug: 328085852 Change-Id: I17df8ae1fcf5e21662e582e795d00a3d800640f3 Test: presubmit --- .../Scheduler/VSyncPredictor.cpp | 45 +++++++++---------- .../tests/unittests/SchedulerTest.cpp | 9 ++-- .../tests/unittests/VSyncPredictorTest.cpp | 44 ++++++++++++++++++ 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 2f9dfeaff7..45b2961dbd 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -45,28 +45,6 @@ using base::StringAppendF; static auto constexpr kMaxPercent = 100u; -namespace { -nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime, - std::optional lastVsyncOpt) { - const auto threshold = model.slope / 2; - - if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { - const auto vsyncDiff = vsyncTime - *lastVsyncOpt; - if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) { - const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; - ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. " - "adjust by %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, - static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, - static_cast(vsyncFixup) / 1e6f); - return vsyncFixup; - } - } - - return 0; -} -} // namespace - VSyncPredictor::~VSyncPredictor() = default; VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNull modePtr, @@ -585,16 +563,33 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime std::optional lastVsyncOpt) { ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA"); + nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); const auto threshold = model.slope / 2; const auto lastFrameMissed = lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; - nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); nsecs_t vsyncFixupTime = 0; if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { + // If the last frame missed is the last vsync, we already shifted the timeline. Depends on + // whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a different + // fixup. There is no need to to shift the vsync timeline again. vsyncTime += missedVsync.fixup.ns(); ATRACE_FORMAT_INSTANT("lastFrameMissed"); } else { - vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt); + if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { + // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it + // first before trying to use it. + lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + const auto vsyncDiff = vsyncTime - *lastVsyncOpt; + if (vsyncDiff <= minFramePeriod.ns() - threshold) { + vsyncFixupTime = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; + ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from " + "prev. " + "adjust by %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, + static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, + static_cast(vsyncFixupTime) / 1e6f); + } + } vsyncTime += vsyncFixupTime; } @@ -605,6 +600,8 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime return std::nullopt; } + // If we needed a fixup, it means that we changed the render rate and the chosen vsync would + // cross minFramePeriod. In that case we need to shift the entire vsync timeline. if (vsyncFixupTime > 0) { shiftVsyncSequence(Duration::fromNs(vsyncFixupTime)); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 049b0923a6..e69410b604 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -591,6 +591,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { TimePoint::fromNs(2000))); // Not crossing the min frame period + vrrTracker->onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500)); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2500))); @@ -599,15 +600,15 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); - // Set 2000 as vsync seq #0 - vrrTracker->nextAnticipatedVSyncTimeFrom(1700); + // Set 4000 as vsync seq #0 + vrrTracker->nextAnticipatedVSyncTimeFrom(3700); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(2000))); + TimePoint::fromNs(4000))); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(4000))); + TimePoint::fromNs(6000))); } TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 6b9ea56062..1394c556d4 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -688,6 +688,50 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000)); } +TEST_F(VSyncPredictorTest, adjustsVrrTimelineTwoClients) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto refreshRate = Fps::fromPeriodNsecs(500); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + vrrTracker.setRenderRate(minFrameRate); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700)); + + EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 3000)); + + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700)); + vrrTracker.onFrameBegin(TimePoint::fromNs(3000), TimePoint::fromNs(0)); + + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3700)); + vrrTracker.onFrameMissed(TimePoint::fromNs(4000)); + + EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + vrrTracker.onFrameBegin(TimePoint::fromNs(4500), TimePoint::fromNs(4500)); + + EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); + EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); + vrrTracker.onFrameBegin(TimePoint::fromNs(5500), TimePoint::fromNs(4500)); + + EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(7500, 7500)); + EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5500, 5500)); + vrrTracker.onFrameBegin(TimePoint::fromNs(6500), TimePoint::fromNs(5500)); +} + TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { tracker.addVsyncTimestamp(1000); -- GitLab From 77b4fb1e91144f60dddc027612c237d79fe3776f Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 5 Mar 2024 17:51:53 -0800 Subject: [PATCH 039/465] SF: reduce latency when switching low to high render rate Bug: 328086969 Test: presubmit Change-Id: I5aa1863d7c93d01ffd5c57182b1e085cb52183fa --- .../Scheduler/VSyncPredictor.cpp | 52 +++++-- .../surfaceflinger/Scheduler/VSyncPredictor.h | 3 +- .../tests/unittests/SchedulerTest.cpp | 14 +- .../tests/unittests/VSyncPredictorTest.cpp | 140 ++++++++++++++++-- 4 files changed, 180 insertions(+), 29 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 45b2961dbd..156cf9907e 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -296,12 +296,15 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, const auto now = TimePoint::fromNs(mClock->now()); purgeTimelines(now); + const auto model = getVSyncPredictionModelLocked(); + const auto threshold = model.slope / 2; std::optional vsyncOpt; for (auto& timeline : mTimelines) { - vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(), - minFramePeriodLocked(), + vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodLocked(), snapToVsync(timePoint), mMissedVsync, - lastVsyncOpt); + lastVsyncOpt ? snapToVsync(*lastVsyncOpt - + threshold) + : lastVsyncOpt); if (vsyncOpt) { break; } @@ -353,9 +356,19 @@ void VSyncPredictor::setRenderRate(Fps renderRate) { ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str()); ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str()); std::lock_guard lock(mMutex); + const auto prevRenderRate = mRenderRateOpt; mRenderRateOpt = renderRate; - mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); - mTimelines.emplace_back(mIdealPeriod, renderRate); + const auto renderPeriodDelta = + prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0; + if (renderPeriodDelta > renderRate.getPeriodNsecs() && + mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs()) { + mTimelines.clear(); + mLastCommittedVsync = TimePoint::fromNs(0); + } else { + mTimelines.back().freeze( + TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); + } + mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate); purgeTimelines(TimePoint::fromNs(mClock->now())); } @@ -507,7 +520,7 @@ void VSyncPredictor::clearTimestamps() { mTimelines.clear(); mLastCommittedVsync = TimePoint::fromNs(0); mIdealPeriod = Period::fromNs(idealPeriod()); - mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt); + mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt); } bool VSyncPredictor::needsMoreSamples() const { @@ -535,6 +548,16 @@ void VSyncPredictor::dump(std::string& result) const { } void VSyncPredictor::purgeTimelines(android::TimePoint now) { + const auto kEnoughFramesToBreakPhase = 5; + if (mRenderRateOpt && + mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase < + mClock->now()) { + mTimelines.clear(); + mLastCommittedVsync = TimePoint::fromNs(0); + mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt); + return; + } + while (mTimelines.size() > 1) { const auto validUntilOpt = mTimelines.front().validUntil(); if (validUntilOpt && *validUntilOpt < now) { @@ -547,8 +570,17 @@ void VSyncPredictor::purgeTimelines(android::TimePoint now) { LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value()); } -VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional renderRateOpt) - : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {} +auto VSyncPredictor::VsyncTimeline::makeVsyncSequence(TimePoint knownVsync) + -> std::optional { + if (knownVsync.ns() == 0) return std::nullopt; + return std::make_optional({knownVsync.ns(), 0}); +} + +VSyncPredictor::VsyncTimeline::VsyncTimeline(TimePoint knownVsync, Period idealPeriod, + std::optional renderRateOpt) + : mIdealPeriod(idealPeriod), + mRenderRateOpt(renderRateOpt), + mLastVsyncSequence(makeVsyncSequence(knownVsync)) {} void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) { LOG_ALWAYS_FATAL_IF(mValidUntil.has_value()); @@ -578,7 +610,9 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it // first before trying to use it. - lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + if (mLastVsyncSequence->seq > 0) { + lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + } const auto vsyncDiff = vsyncTime - *lastVsyncOpt; if (vsyncDiff <= minFramePeriod.ns() - threshold) { vsyncFixupTime = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index c175765485..a1e72d1426 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -89,7 +89,7 @@ private: class VsyncTimeline { public: - VsyncTimeline(Period idealPeriod, std::optional renderRateOpt); + VsyncTimeline(TimePoint knownVsync, Period idealPeriod, std::optional renderRateOpt); std::optional nextAnticipatedVSyncTimeFrom( Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, std::optional lastVsyncOpt = {}); @@ -101,6 +101,7 @@ private: private: nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync); + std::optional makeVsyncSequence(TimePoint knownVsync); const Period mIdealPeriod = Duration::fromNs(0); const std::optional mRenderRateOpt; diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index e69410b604..2d3796abed 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -57,6 +57,11 @@ using LayerHierarchy = surfaceflinger::frontend::LayerHierarchy; using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder; using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState; +class ZeroClock : public Clock { +public: + nsecs_t now() const override { return 0; } +}; + class SchedulerTest : public testing::Test { protected: class MockEventThreadConnection : public android::EventThreadConnection { @@ -564,7 +569,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { hal::VrrConfig{.minFrameIntervalNs = static_cast( frameRate.getPeriodNsecs())})); std::shared_ptr vrrTracker = - std::make_shared(std::make_unique(), kMode, kHistorySize, + std::make_shared(std::make_unique(), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr vrrSelectorPtr = @@ -600,15 +605,12 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); - // Set 4000 as vsync seq #0 - vrrTracker->nextAnticipatedVSyncTimeFrom(3700); - EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(4000))); + TimePoint::fromNs(4500))); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(6000))); + TimePoint::fromNs(6500))); } TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 1394c556d4..b58ac66e98 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -622,13 +622,13 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 6 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 6 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { @@ -651,6 +651,119 @@ TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } +TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto vsyncRate = Fps::fromPeriodNsecs(500); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000)); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000)); + + // commit to a vsync in the future + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000)); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); + + EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(3500)); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); + EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000)); + EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); + EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000)); + EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2500)); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); + EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000)); + EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); + EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000)); + EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); + EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000)); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); + EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000)); + EXPECT_EQ(11000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); + EXPECT_EQ(13000, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000)); + EXPECT_EQ(17000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); + EXPECT_EQ(20000, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); +} + +TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto vsyncRate = Fps::fromPeriodNsecs(500); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(5000)); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4700)); + EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + + mClock->setNow(50000); + EXPECT_EQ(50500, vrrTracker.nextAnticipatedVSyncTimeFrom(50000, 10000)); +} + +TEST_F(VSyncPredictorTest, returnsCorrectVsyncWhenLastIsNot) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto vsyncRate = Fps::fromPeriodNsecs(500); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000)); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234)); +} + TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { SET_FLAG_FOR_TEST(flags::vrr_config, true); @@ -743,17 +856,18 @@ TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); tracker.setRenderRate(Fps::fromPeriodNsecs(3000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000)); -- GitLab From ee6365b1d1704b1eedfb8f81e471b80692d8ec43 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 6 Mar 2024 14:31:45 -0800 Subject: [PATCH 040/465] SF: flush setRenderRate when changing mode There is no point to try to syncronize changing the render rate if the display mode changes. This would just cause more latency to getting the next vsync. Bug: 328140524 Test: presubmit Change-Id: I78a82bee01ba793890d459564ab701d257851b49 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 4 +- services/surfaceflinger/Scheduler/Scheduler.h | 2 +- .../Scheduler/VSyncPredictor.cpp | 11 +++- .../surfaceflinger/Scheduler/VSyncPredictor.h | 2 +- .../surfaceflinger/Scheduler/VSyncTracker.h | 4 +- services/surfaceflinger/SurfaceFlinger.cpp | 11 ++-- .../tests/unittests/SchedulerTest.cpp | 4 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 2 +- .../tests/unittests/VSyncPredictorTest.cpp | 60 ++++++++++++++----- .../tests/unittests/mock/MockVSyncTracker.h | 2 +- 10 files changed, 71 insertions(+), 31 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 3f9168252b..d92edb81e0 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -565,7 +565,7 @@ void Scheduler::onHardwareVsyncRequest(PhysicalDisplayId id, bool enabled) { })); } -void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { +void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate, bool applyImmediately) { std::scoped_lock lock(mDisplayLock); ftl::FakeGuard guard(kMainThreadContext); @@ -586,7 +586,7 @@ void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) { ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(), to_string(mode.modePtr->getVsyncRate()).c_str()); - display.schedulePtr->getTracker().setRenderRate(renderFrameRate); + display.schedulePtr->getTracker().setRenderRate(renderFrameRate, applyImmediately); } Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id, diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 09f75fdaca..494a91bf21 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -188,7 +188,7 @@ public: const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; } // Sets the render rate for the scheduler to run at. - void setRenderRate(PhysicalDisplayId, Fps); + void setRenderRate(PhysicalDisplayId, Fps, bool applyImmediately); void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext); void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 156cf9907e..58457d82fc 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -296,6 +296,10 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, const auto now = TimePoint::fromNs(mClock->now()); purgeTimelines(now); + if (lastVsyncOpt && *lastVsyncOpt > timePoint) { + timePoint = *lastVsyncOpt; + } + const auto model = getVSyncPredictionModelLocked(); const auto threshold = model.slope / 2; std::optional vsyncOpt; @@ -352,7 +356,7 @@ bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) { return mTimelines.back().isVSyncInPhase(model, vsync, frameRate); } -void VSyncPredictor::setRenderRate(Fps renderRate) { +void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) { ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str()); ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str()); std::lock_guard lock(mMutex); @@ -360,8 +364,9 @@ void VSyncPredictor::setRenderRate(Fps renderRate) { mRenderRateOpt = renderRate; const auto renderPeriodDelta = prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0; - if (renderPeriodDelta > renderRate.getPeriodNsecs() && - mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs()) { + const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() && + mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs(); + if (applyImmediately || newRenderRateIsHigher) { mTimelines.clear(); mLastCommittedVsync = TimePoint::fromNs(0); } else { diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index a1e72d1426..21039f14d9 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -68,7 +68,7 @@ public: void setDisplayModePtr(ftl::NonNull) final EXCLUDES(mMutex); - void setRenderRate(Fps) final EXCLUDES(mMutex); + void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex); void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final EXCLUDES(mMutex); diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 1e55a87254..8787cdb82f 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -102,8 +102,10 @@ public: * when a display is running at 120Hz but the render frame rate is 60Hz. * * \param [in] Fps The render rate the tracker should operate at. + * \param [in] applyImmediately Whether to apply the new render rate immediately regardless of + * already committed vsyncs. */ - virtual void setRenderRate(Fps) = 0; + virtual void setRenderRate(Fps, bool applyImmediately) = 0; virtual void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) = 0; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 040bf55e0c..da423b80c2 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1239,8 +1239,8 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) { switch (display->setDesiredMode(std::move(desiredMode))) { case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch: // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler. - mScheduler->setRenderRate(displayId, - display->refreshRateSelector().getActiveMode().fps); + mScheduler->setRenderRate(displayId, display->refreshRateSelector().getActiveMode().fps, + /*applyImmediately*/ true); // Schedule a new frame to initiate the display mode switch. scheduleComposite(FrameHint::kNone); @@ -1261,7 +1261,7 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) { mScheduler->setModeChangePending(true); break; case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch: - mScheduler->setRenderRate(displayId, mode.fps); + mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false); if (displayId == mActiveDisplayId) { mScheduler->updatePhaseConfiguration(mode.fps); @@ -1382,7 +1382,7 @@ void SurfaceFlinger::applyActiveMode(const sp& display) { constexpr bool kAllowToEnable = true; mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take()); - mScheduler->setRenderRate(displayId, renderFps); + mScheduler->setRenderRate(displayId, renderFps, /*applyImmediately*/ true); if (displayId == mActiveDisplayId) { mScheduler->updatePhaseConfiguration(renderFps); @@ -4376,7 +4376,8 @@ void SurfaceFlinger::initScheduler(const sp& display) { // The pacesetter must be registered before EventThread creation below. mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); if (FlagManager::getInstance().vrr_config()) { - mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps); + mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps, + /*applyImmediately*/ true); } const auto configs = mScheduler->getVsyncConfiguration().getCurrentConfigs(); diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 2d3796abed..d4735c7558 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -583,7 +583,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); - scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); + scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false); vrrTracker->addVsyncTimestamp(0); // Set 1000 as vsync seq #0 vrrTracker->nextAnticipatedVSyncTimeFrom(700); @@ -603,7 +603,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { // Change render rate frameRate = Fps::fromPeriodNsecs(2000); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); - scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); + scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index c22deaba72..d701a97b7d 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -50,7 +50,7 @@ public: bool needsMoreSamples() const final { return false; } bool isVSyncInPhase(nsecs_t, Fps) final { return false; } void setDisplayModePtr(ftl::NonNull) final {} - void setRenderRate(Fps) final {} + void setRenderRate(Fps, bool) final {} void onFrameBegin(TimePoint, TimePoint) final {} void onFrameMissed(TimePoint) final {} void dump(std::string&) const final {} diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index b58ac66e98..6559fa5d76 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -620,7 +620,7 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { tracker.addVsyncTimestamp(mNow); } - tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod)); + tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod), /*applyImmediately*/ false); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + 3 * mPeriod)); @@ -640,7 +640,7 @@ TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { tracker.addVsyncTimestamp(mNow); } - tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod)); + tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod), /*applyImmediately*/ false); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod)); @@ -651,7 +651,7 @@ TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod)); } -TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { +TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) { SET_FLAG_FOR_TEST(flags::vrr_config, true); const int32_t kGroup = 0; @@ -669,7 +669,7 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000)); @@ -677,14 +677,14 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { // commit to a vsync in the future EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(3500)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(3500), /*applyImmediately*/ false); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); @@ -693,7 +693,7 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000)); EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2500)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2500), /*applyImmediately*/ false); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); @@ -703,7 +703,7 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); @@ -714,6 +714,38 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediatley) { EXPECT_EQ(20000, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); } +TEST_F(VSyncPredictorTest, setRenderRateExplicitAppliedImmediately) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto vsyncRate = Fps::fromPeriodNsecs(500); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000)); + + // commit to a vsync in the future + EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 2000)); + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true); + EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000)); + EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 4500)); + EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6500)); +} + TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) { SET_FLAG_FOR_TEST(flags::vrr_config, true); @@ -732,7 +764,7 @@ TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) { VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(5000)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(5000), /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4700)); EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); @@ -759,7 +791,7 @@ TEST_F(VSyncPredictorTest, returnsCorrectVsyncWhenLastIsNot) { VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; - vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000)); + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234)); } @@ -782,7 +814,7 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; - vrrTracker.setRenderRate(minFrameRate); + vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000)); @@ -819,7 +851,7 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimelineTwoClients) { VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; - vrrTracker.setRenderRate(minFrameRate); + vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700)); @@ -852,7 +884,7 @@ TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - tracker.setRenderRate(Fps::fromPeriodNsecs(2000)); + tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); @@ -861,7 +893,7 @@ TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); - tracker.setRenderRate(Fps::fromPeriodNsecs(3000)); + tracker.setRenderRate(Fps::fromPeriodNsecs(3000), /*applyImmediately*/ false); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 6d10a5cc5d..c311901c7a 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -36,7 +36,7 @@ public: MOCK_METHOD(bool, needsMoreSamples, (), (const, override)); MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override)); MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull), (override)); - MOCK_METHOD(void, setRenderRate, (Fps), (override)); + MOCK_METHOD(void, setRenderRate, (Fps, bool), (override)); MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); MOCK_METHOD(void, onFrameMissed, (TimePoint), (override)); MOCK_METHOD(void, dump, (std::string&), (const, override)); -- GitLab From 940b7a60c29646c8424d479323a495ffcc45ded5 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 7 Mar 2024 10:04:27 -0800 Subject: [PATCH 041/465] SF: don't use minFramePeriod if it is same as vsync Bug: 328140524 Test: presubmit Change-Id: Ie1d8fd1af508e567fb1340727e37d89c99ec7c6b --- .../Scheduler/VSyncPredictor.cpp | 44 ++++++++++++++----- .../surfaceflinger/Scheduler/VSyncPredictor.h | 5 ++- .../tests/unittests/VSyncPredictorTest.cpp | 37 +++++++++++++++- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 58457d82fc..3fc9a07b6c 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -45,6 +45,15 @@ using base::StringAppendF; static auto constexpr kMaxPercent = 100u; +namespace { +int numVsyncsPerFrame(const ftl::NonNull& displayModePtr) { + const auto idealPeakRefreshPeriod = displayModePtr->getPeakFps().getPeriodNsecs(); + const auto idealRefreshPeriod = displayModePtr->getVsyncRate().getPeriodNsecs(); + return static_cast(std::round(static_cast(idealPeakRefreshPeriod) / + static_cast(idealRefreshPeriod))); +} +} // namespace + VSyncPredictor::~VSyncPredictor() = default; VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNull modePtr, @@ -56,7 +65,8 @@ VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNullgetPeakFps().getPeriodNsecs(); - const auto numPeriods = static_cast(std::round(static_cast(idealPeakRefreshPeriod) / - static_cast(idealPeriod()))); const auto slope = mRateMap.find(idealPeriod())->second.slope; - return Period::fromNs(slope * numPeriods); + return Period::fromNs(slope * mNumVsyncsForFrame); } bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { @@ -302,9 +309,15 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, const auto model = getVSyncPredictionModelLocked(); const auto threshold = model.slope / 2; + std::optional minFramePeriodOpt; + + if (mNumVsyncsForFrame > 1) { + minFramePeriodOpt = minFramePeriodLocked(); + } + std::optional vsyncOpt; for (auto& timeline : mTimelines) { - vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodLocked(), + vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodOpt, snapToVsync(timePoint), mMissedVsync, lastVsyncOpt ? snapToVsync(*lastVsyncOpt - threshold) @@ -390,6 +403,7 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { std::lock_guard lock(mMutex); mDisplayModePtr = modePtr; + mNumVsyncsForFrame = numVsyncsPerFrame(mDisplayModePtr); traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs()); static constexpr size_t kSizeLimit = 30; @@ -407,6 +421,11 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) { ATRACE_CALL(); + + if (mNumVsyncsForFrame <= 1) { + return 0ns; + } + const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; const auto threshold = currentPeriod / 2; const auto minFramePeriod = minFramePeriodLocked().ns(); @@ -596,8 +615,8 @@ void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) { } std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom( - Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync, - std::optional lastVsyncOpt) { + Model model, std::optional minFramePeriodOpt, nsecs_t vsync, + MissedVsync missedVsync, std::optional lastVsyncOpt) { ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA"); nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); @@ -611,7 +630,7 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime // fixup. There is no need to to shift the vsync timeline again. vsyncTime += missedVsync.fixup.ns(); ATRACE_FORMAT_INSTANT("lastFrameMissed"); - } else { + } else if (minFramePeriodOpt) { if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it // first before trying to use it. @@ -619,9 +638,10 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); } const auto vsyncDiff = vsyncTime - *lastVsyncOpt; - if (vsyncDiff <= minFramePeriod.ns() - threshold) { - vsyncFixupTime = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; - ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from " + if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) { + vsyncFixupTime = *lastVsyncOpt + minFramePeriodOpt->ns() - vsyncTime; + ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f " + "from " "prev. " "adjust by %.2f", static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index 21039f14d9..c840cbdd2b 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -91,8 +91,8 @@ private: public: VsyncTimeline(TimePoint knownVsync, Period idealPeriod, std::optional renderRateOpt); std::optional nextAnticipatedVSyncTimeFrom( - Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, - std::optional lastVsyncOpt = {}); + Model model, std::optional minFramePeriodOpt, nsecs_t vsyncTime, + MissedVsync lastMissedVsync, std::optional lastVsyncOpt = {}); void freeze(TimePoint lastVsync); std::optional validUntil() const { return mValidUntil; } bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate); @@ -144,6 +144,7 @@ private: std::vector mTimestamps GUARDED_BY(mMutex); ftl::NonNull mDisplayModePtr GUARDED_BY(mMutex); + int mNumVsyncsForFrame GUARDED_BY(mMutex); std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 6559fa5d76..daea28e272 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -714,6 +714,33 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) { EXPECT_EQ(20000, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); } +TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) { + SET_FLAG_FOR_TEST(flags::vrr_config, true); + + const int32_t kGroup = 0; + const auto kResolution = ui::Size(1920, 1080); + const auto vsyncRate = Fps::fromPeriodNsecs(1000); + const auto minFrameRate = Fps::fromPeriodNsecs(1000); + hal::VrrConfig vrrConfig; + vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs(); + const ftl::NonNull kMode = + ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup, + kResolution, DEFAULT_DISPLAY_ID) + .setVrrConfig(std::move(vrrConfig)) + .build()); + + VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, + kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + + vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); + vrrTracker.addVsyncTimestamp(0); + EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700)); + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000)); + + // Assume that the last vsync is wrong due to a vsync drift. It shouldn't matter. + EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1700)); +} + TEST_F(VSyncPredictorTest, setRenderRateExplicitAppliedImmediately) { SET_FLAG_FOR_TEST(flags::vrr_config, true); @@ -853,21 +880,27 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimelineTwoClients) { vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); - EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700)); + // App runs ahead + EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700)); EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 3000)); - EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + + // SF starts to catch up EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700)); vrrTracker.onFrameBegin(TimePoint::fromNs(3000), TimePoint::fromNs(0)); + // SF misses last frame (3000) and observes that when committing (4000) EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3700)); vrrTracker.onFrameMissed(TimePoint::fromNs(4000)); + // SF wakes up again instead of the (4000) missed frame EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); vrrTracker.onFrameBegin(TimePoint::fromNs(4500), TimePoint::fromNs(4500)); + // Timeline shifted. The app needs to get the next frame at (7500) as its last frame (6500) will + // be presented at (7500) EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); vrrTracker.onFrameBegin(TimePoint::fromNs(5500), TimePoint::fromNs(4500)); -- GitLab From 995f4cffe2f7f984c6b3d8416e224b2868acd8a5 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 7 Mar 2024 19:40:03 -0800 Subject: [PATCH 042/465] Force repaint when color matrix changes If teh color matrix changes, force a repaint otherwise the changes will be delayed. Fixes: 328675276 Test: update brightness slider and confirm color matrix changes are applied immediately Change-Id: I195a18644ccca55e89f112434d8374e156f58e36 --- services/surfaceflinger/SurfaceFlinger.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cf5f55d7bd..6ee1c7bd2a 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2731,7 +2731,8 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( refreshArgs.forceOutputColorMode = mForceColorMode; refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty; - refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty; + refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || + mVisibleRegionsDirty || mDrawingState.colorMatrixChanged; refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags(); if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) { -- GitLab From a67623c3daa81ecfe707c131f218bd1d0f416684 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 21 Feb 2024 06:57:36 +0000 Subject: [PATCH 043/465] InputTracer: Trace events derived from other events separately InputDispatcher goes through multiple "phases" when dispatching new event, roughly corresponding to the following: 1. Inbound event processing (e.g. InputFilter, policy filtering, generating key repeats, etc.) 2. Target finding (finding the touched/focused window(s) that should receive the event) 3. Event modification (generating new events based on the original, such as for split motions) 4. Publishing When an event is modified in step 3, we always create a new EventEntry with a new event ID to distinguish it from the original. These derived events need to be traced separately, but need to share the same trace context as the original event. For example, an event is split across windows A and B, and the whole event is sent to spy window C. In this case, windows A and B receive a derived event, and C receives the whole event. If B is a trace-sensitive window, we must not leak the sensitive info from the event by tracing the original event through C. Since event modification (step 3) always happens after target finding (step 2) for a dispatch entry, we will trace the derived (modified) events separately. The modified event will never affect target-finding, so the derived events are more limited, but they share the same context as the original event by using the same State under the hood. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I772a04b7dfd0322357dd4dfa95387244ca6230e9 --- .../dispatcher/InputDispatcher.cpp | 60 +++++++++---- .../dispatcher/trace/InputTracer.cpp | 87 ++++++++++++++----- .../dispatcher/trace/InputTracer.h | 25 ++++-- .../dispatcher/trace/InputTracerInterface.h | 14 ++- 4 files changed, 134 insertions(+), 52 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 73bbed6c68..56b0c8f12c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -399,7 +399,8 @@ std::unique_ptr createDispatchEntry(const IdGenerator& idGenerato const InputTarget& inputTarget, std::shared_ptr eventEntry, ftl::Flags inputTargetFlags, - int64_t vsyncId) { + int64_t vsyncId, + trace::InputTracerInterface* tracer) { const bool zeroCoords = inputTargetFlags.test(InputTarget::Flags::ZERO_COORDS); const sp win = inputTarget.windowHandle; const std::optional windowId = @@ -462,6 +463,10 @@ std::unique_ptr createDispatchEntry(const IdGenerator& idGenerato motionEntry.xCursorPosition, motionEntry.yCursorPosition, motionEntry.downTime, motionEntry.pointerProperties, pointerCoords); + if (tracer) { + combinedMotionEntry->traceTracker = + tracer->traceDerivedEvent(*combinedMotionEntry, *motionEntry.traceTracker); + } std::unique_ptr dispatchEntry = std::make_unique(std::move(combinedMotionEntry), inputTargetFlags, @@ -3386,7 +3391,7 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr dispatchEntry = createDispatchEntry(mIdGenerator, inputTarget, eventEntry, inputTarget.flags, - mWindowInfosVsyncId); + mWindowInfosVsyncId, mTracer.get()); // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a // different EventEntry than what was passed in. @@ -3469,21 +3474,31 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptrsecond; } } - // Generate a new MotionEntry with a new eventId using the resolved action and - // flags. - resolvedMotion = std::make_shared< - MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState, - motionEntry.eventTime, motionEntry.deviceId, - motionEntry.source, motionEntry.displayId, - motionEntry.policyFlags, resolvedAction, - motionEntry.actionButton, resolvedFlags, - motionEntry.metaState, motionEntry.buttonState, - motionEntry.classification, motionEntry.edgeFlags, - motionEntry.xPrecision, motionEntry.yPrecision, - motionEntry.xCursorPosition, motionEntry.yCursorPosition, - motionEntry.downTime, - usingProperties.value_or(motionEntry.pointerProperties), - usingCoords.value_or(motionEntry.pointerCoords)); + { + // Generate a new MotionEntry with a new eventId using the resolved action + // and flags, and set it as the resolved entry. + auto newEntry = std::make_shared< + MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState, + motionEntry.eventTime, motionEntry.deviceId, + motionEntry.source, motionEntry.displayId, + motionEntry.policyFlags, resolvedAction, + motionEntry.actionButton, resolvedFlags, + motionEntry.metaState, motionEntry.buttonState, + motionEntry.classification, motionEntry.edgeFlags, + motionEntry.xPrecision, motionEntry.yPrecision, + motionEntry.xCursorPosition, + motionEntry.yCursorPosition, motionEntry.downTime, + usingProperties.value_or( + motionEntry.pointerProperties), + usingCoords.value_or(motionEntry.pointerCoords)); + if (mTracer) { + ensureEventTraced(motionEntry); + newEntry->traceTracker = + mTracer->traceDerivedEvent(*newEntry, + *motionEntry.traceTracker); + } + resolvedMotion = newEntry; + } if (ATRACE_ENABLED()) { std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32 ") to MotionEvent(id=0x%" PRIx32 ").", @@ -3508,7 +3523,8 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptrgetDescription(); std::unique_ptr cancelDispatchEntry = createDispatchEntry(mIdGenerator, inputTarget, std::move(cancelEvent), - ftl::Flags(), mWindowInfosVsyncId); + ftl::Flags(), mWindowInfosVsyncId, + mTracer.get()); // Send these cancel events to the queue before sending the event from the new // device. @@ -4316,6 +4332,10 @@ std::unique_ptr InputDispatcher::splitMotionEvent( originalMotionEntry.xCursorPosition, originalMotionEntry.yCursorPosition, splitDownTime, pointerProperties, pointerCoords); + if (mTracer) { + splitMotionEntry->traceTracker = + mTracer->traceDerivedEvent(*splitMotionEntry, *originalMotionEntry.traceTracker); + } return splitMotionEntry; } @@ -6614,6 +6634,10 @@ std::unique_ptr InputDispatcher::afterKeyEventLockedInterruptabl *fallbackKeyCode, event.getScanCode(), event.getMetaState(), event.getRepeatCount(), event.getDownTime()); + if (mTracer) { + newEntry->traceTracker = + mTracer->traceDerivedEvent(*newEntry, *keyEntry.traceTracker); + } if (DEBUG_OUTBOUND_EVENT_DETAILS) { ALOGD("Unhandled key event: Dispatching fallback key. " "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x", diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 10f6b0f768..3b5a09694c 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -59,6 +59,12 @@ TracedEvent createTracedEvent(const KeyEntry& e) { e.downTime, e.flags, e.repeatCount}; } +void writeEventToBackend(const TracedEvent& event, InputTracingBackendInterface& backend) { + std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e); }, + [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e); }}, + event); +} + } // namespace // --- InputTracer --- @@ -67,41 +73,74 @@ InputTracer::InputTracer(std::unique_ptr backend) : mBackend(std::move(backend)) {} std::unique_ptr InputTracer::traceInboundEvent(const EventEntry& entry) { - TracedEvent traced; + // This is a newly traced inbound event. Create a new state to track it and its derived events. + auto eventState = std::make_shared(*this); if (entry.type == EventEntry::Type::MOTION) { const auto& motion = static_cast(entry); - traced = createTracedEvent(motion); + eventState->events.emplace_back(createTracedEvent(motion)); } else if (entry.type == EventEntry::Type::KEY) { const auto& key = static_cast(entry); - traced = createTracedEvent(key); + eventState->events.emplace_back(createTracedEvent(key)); } else { LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); } - return std::make_unique(*this, std::move(traced)); + return std::make_unique(std::move(eventState), /*isDerived=*/false); } void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, const InputTarget& target) { + if (isDerivedCookie(cookie)) { + LOG(FATAL) << "Event target cannot be updated from a derived cookie."; + } auto& eventState = getState(cookie); - if (eventState.isEventProcessingComplete) { - LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()"; + if (eventState->isEventProcessingComplete) { + // TODO(b/210460522): Disallow adding new targets after eventProcessingComplete() is called. + return; } // TODO(b/210460522): Determine if the event is sensitive based on the target. } void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { + if (isDerivedCookie(cookie)) { + LOG(FATAL) << "Event processing cannot be set from a derived cookie."; + } auto& eventState = getState(cookie); - if (eventState.isEventProcessingComplete) { + if (eventState->isEventProcessingComplete) { LOG(FATAL) << "Traced event was already logged. " "eventProcessingComplete() was likely called more than once."; } - std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); }, - [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }}, - eventState.event); - eventState.isEventProcessingComplete = true; + for (const auto& event : eventState->events) { + writeEventToBackend(event, *mBackend); + } + eventState->isEventProcessingComplete = true; +} + +std::unique_ptr InputTracer::traceDerivedEvent( + const EventEntry& entry, const EventTrackerInterface& originalEventCookie) { + // This is an event derived from an already-established event. Use the same state to track + // this event too. + auto eventState = getState(originalEventCookie); + + if (entry.type == EventEntry::Type::MOTION) { + const auto& motion = static_cast(entry); + eventState->events.emplace_back(createTracedEvent(motion)); + } else if (entry.type == EventEntry::Type::KEY) { + const auto& key = static_cast(entry); + eventState->events.emplace_back(createTracedEvent(key)); + } else { + LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); + } + + if (eventState->isEventProcessingComplete) { + // It is possible for a derived event to be dispatched some time after the original event + // is dispatched, such as in the case of key fallback events. To account for these cases, + // derived events can be traced after the processing is complete for the original event. + writeEventToBackend(eventState->events.back(), *mBackend); + } + return std::make_unique(std::move(eventState), /*isDerived=*/true); } void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, @@ -126,9 +165,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, if (!cookie) { // This event was not tracked as an inbound event, so trace it now. - std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); }, - [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }}, - traced); + writeEventToBackend(traced, *mBackend); } // The vsyncId only has meaning if the event is targeting a window. @@ -141,27 +178,29 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}, resolvedKeyRepeatCount}); } -InputTracer::EventState& InputTracer::getState(const EventTrackerInterface& cookie) { +std::shared_ptr& InputTracer::getState( + const EventTrackerInterface& cookie) { return static_cast(cookie).mState; } -// --- InputTracer::EventTrackerImpl --- +bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { + return static_cast(cookie).mIsDerived; +} -InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event) - : mTracer(tracer), mState(event) {} +// --- InputTracer::EventState --- -InputTracer::EventTrackerImpl::~EventTrackerImpl() { - if (mState.isEventProcessingComplete) { +InputTracer::EventState::~EventState() { + if (isEventProcessingComplete) { // This event has already been written to the trace as expected. return; } // The event processing was never marked as complete, so do it now. // TODO(b/210460522): Determine why/where the event is being destroyed before // eventProcessingComplete() is called. - std::visit(Visitor{[&](const TracedMotionEvent& e) { mTracer.mBackend->traceMotionEvent(e); }, - [&](const TracedKeyEvent& e) { mTracer.mBackend->traceKeyEvent(e); }}, - mState.event); - mState.isEventProcessingComplete = true; + for (const auto& event : events) { + writeEventToBackend(event, *tracer.mBackend); + } + isEventProcessingComplete = true; } } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 1acac6df20..ccff30e59d 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -44,36 +44,43 @@ public: std::unique_ptr traceInboundEvent(const EventEntry&) override; void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override; void eventProcessingComplete(const EventTrackerInterface&) override; + std::unique_ptr traceDerivedEvent(const EventEntry&, + const EventTrackerInterface&) override; void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override; private: std::unique_ptr mBackend; - // The state of a tracked event. + // The state of a tracked event, shared across all events derived from the original event. struct EventState { - explicit inline EventState(TracedEvent event) : event(std::move(event)){}; + explicit inline EventState(InputTracer& tracer) : tracer(tracer){}; + ~EventState(); - const TracedEvent event; + InputTracer& tracer; + std::vector events; bool isEventProcessingComplete{false}; // TODO(b/210460522): Add additional args for tracking event sensitivity and // dispatch target UIDs. }; // Get the event state associated with a tracking cookie. - EventState& getState(const EventTrackerInterface&); + std::shared_ptr& getState(const EventTrackerInterface&); + bool isDerivedCookie(const EventTrackerInterface&); // Implementation of the event tracker cookie. The cookie holds the event state directly for // convenience to avoid the overhead of tracking the state separately in InputTracer. class EventTrackerImpl : public EventTrackerInterface { public: - explicit EventTrackerImpl(InputTracer&, TracedEvent&& entry); - virtual ~EventTrackerImpl() override; + inline EventTrackerImpl(const std::shared_ptr& state, bool isDerivedEvent) + : mState(state), mIsDerived(isDerivedEvent) {} + EventTrackerImpl(const EventTrackerImpl&) = default; private: - InputTracer& mTracer; - mutable EventState mState; + mutable std::shared_ptr mState; + const bool mIsDerived; - friend EventState& InputTracer::getState(const EventTrackerInterface&); + friend std::shared_ptr& InputTracer::getState(const EventTrackerInterface&); + friend bool InputTracer::isDerivedCookie(const EventTrackerInterface&); }; }; diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h index c6cd7de4b6..daf716f993 100644 --- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h @@ -52,7 +52,6 @@ public: * to track the event's lifecycle inside InputDispatcher. */ virtual std::unique_ptr traceInboundEvent(const EventEntry&) = 0; - /** * Notify the tracer that the traced event will be sent to the given InputTarget. * The tracer may change how the event is logged depending on the target. For example, @@ -75,6 +74,19 @@ public: */ virtual void eventProcessingComplete(const EventTrackerInterface&) = 0; + /** + * Trace an input event that is derived from another event. This is used in cases where an event + * is modified from the original, such as when a touch is split across multiple windows, or + * when a HOVER_MOVE event is modified to be a HOVER_EXIT, etc. The original event's tracker + * must be provided, and a new EventTracker is returned that should be used to track the event's + * lifecycle. + * + * NOTE: The derived tracker cannot be used to change the targets of the original event, meaning + * it cannot be used with {@link #dispatchToTargetHint} or {@link eventProcessingComplete}. + */ + virtual std::unique_ptr traceDerivedEvent( + const EventEntry&, const EventTrackerInterface& originalEventTracker) = 0; + /** * Trace an input event being successfully dispatched to a window. The dispatched event may * be a previously traced inbound event, or it may be a synthesized event that has not been -- GitLab From 4fc2fcea7641d91c7eb2d0228779d4fc600a274c Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 8 Mar 2024 06:43:44 +0000 Subject: [PATCH 044/465] SF: change the render rate directly instead of starting a new VsyncTimeline So we would preserve the old vsync cadence Bug: 328140524 Change-Id: Ibdd4e49ab5494605e39957cedc6fba00146d18d9 Test: presubmit --- services/surfaceflinger/Scheduler/VSyncPredictor.cpp | 9 ++++++++- services/surfaceflinger/Scheduler/VSyncPredictor.h | 3 ++- .../tests/unittests/VSyncPredictorTest.cpp | 6 +++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 3fc9a07b6c..db1930da54 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -379,9 +379,16 @@ void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) { prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0; const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() && mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs(); - if (applyImmediately || newRenderRateIsHigher) { + if (applyImmediately) { + while (mTimelines.size() > 1) { + mTimelines.pop_front(); + } + + mTimelines.front().setRenderRate(renderRate); + } else if (newRenderRateIsHigher) { mTimelines.clear(); mLastCommittedVsync = TimePoint::fromNs(0); + } else { mTimelines.back().freeze( TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index c840cbdd2b..3ed1d41a01 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -97,6 +97,7 @@ private: std::optional validUntil() const { return mValidUntil; } bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate); void shiftVsyncSequence(Duration phase); + void setRenderRate(Fps renderRate) { mRenderRateOpt = renderRate; } private: nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); @@ -104,7 +105,7 @@ private: std::optional makeVsyncSequence(TimePoint knownVsync); const Period mIdealPeriod = Duration::fromNs(0); - const std::optional mRenderRateOpt; + std::optional mRenderRateOpt; std::optional mValidUntil; std::optional mLastVsyncSequence; }; diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index daea28e272..48707cb6d2 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -768,9 +768,9 @@ TEST_F(VSyncPredictorTest, setRenderRateExplicitAppliedImmediately) { EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 2000)); vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true); - EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000)); - EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 4500)); - EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6500)); + EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000)); + EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(7000, 7000)); } TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) { -- GitLab From f52ad20144219b5ef808d195545c60268317ba80 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Wed, 6 Mar 2024 18:18:28 -0500 Subject: [PATCH 045/465] Refactor SkiaVkRenderEngine's VulkanInterface and DestroySemaphoreInfo ... to be shareable (and more defensive). VulkanInterface is mostly unmodified, other than being converted from a struct to a class. Part of that entails adding getters for a few fields. DestroySemaphoreInfo now handles its own destruction, and supports owning N VkSemaphores (required for Graphite). It also now stores which VulkanInterface it needs to be destroyed with, so that the callback path no longer needs to reference a static VulkanInterface (one tiny step towards b/300533018.) Also incidentally fixed a bug where realtime priority status may have been left uninitialized. Bug: b/293371537 Test: manual testing + existing tests transitively exercise these classes Change-Id: I3a7782d76c72b9ad61f3a1d6968c352a86a2af9f --- libs/renderengine/Android.bp | 1 + libs/renderengine/skia/SkiaVkRenderEngine.cpp | 667 +----------------- libs/renderengine/skia/SkiaVkRenderEngine.h | 37 + libs/renderengine/skia/VulkanInterface.cpp | 582 +++++++++++++++ libs/renderengine/skia/VulkanInterface.h | 95 +++ 5 files changed, 741 insertions(+), 641 deletions(-) create mode 100644 libs/renderengine/skia/VulkanInterface.cpp create mode 100644 libs/renderengine/skia/VulkanInterface.h diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index b501d40f26..09d7cb5d3f 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -87,6 +87,7 @@ filegroup { "skia/SkiaRenderEngine.cpp", "skia/SkiaGLRenderEngine.cpp", "skia/SkiaVkRenderEngine.cpp", + "skia/VulkanInterface.cpp", "skia/debug/CaptureTimer.cpp", "skia/debug/CommonPool.cpp", "skia/debug/SkiaCapture.cpp", diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index eb7a9d5bfa..241ee2cd50 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -32,11 +32,8 @@ #include #include -#include #include -#include #include -#include #include #include "log/log_main.h" @@ -44,619 +41,19 @@ namespace android { namespace renderengine { -struct VulkanFuncs { - PFN_vkCreateSemaphore vkCreateSemaphore = nullptr; - PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr; - PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr; - PFN_vkDestroySemaphore vkDestroySemaphore = nullptr; - - PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr; - PFN_vkDestroyDevice vkDestroyDevice = nullptr; - PFN_vkDestroyInstance vkDestroyInstance = nullptr; -}; - -// Ref-Count a semaphore -struct DestroySemaphoreInfo { - VkSemaphore mSemaphore; - // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia - // (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two refs, one - // owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented each time - // delete_semaphore* is called with this object. Skia will call destroy_semaphore* once it is - // done with the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine - // calls delete_semaphore* after sending the semaphore to Skia and exporting it if need be. - int mRefs = 2; - - DestroySemaphoreInfo(VkSemaphore semaphore) : mSemaphore(semaphore) {} -}; - -namespace { -void onVkDeviceFault(void* callbackContext, const std::string& description, - const std::vector& addressInfos, - const std::vector& vendorInfos, - const std::vector& vendorBinaryData); -} // anonymous namespace - -struct VulkanInterface { - bool initialized = false; - VkInstance instance; - VkPhysicalDevice physicalDevice; - VkDevice device; - VkQueue queue; - int queueIndex; - uint32_t apiVersion; - GrVkExtensions grExtensions; - VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr; - VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr; - VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr; - VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures = nullptr; - GrVkGetProc grGetProc; - bool isProtected; - bool isRealtimePriority; - - VulkanFuncs funcs; - - std::vector instanceExtensionNames; - std::vector deviceExtensionNames; - - GrVkBackendContext getBackendContext() { - GrVkBackendContext backendContext; - backendContext.fInstance = instance; - backendContext.fPhysicalDevice = physicalDevice; - backendContext.fDevice = device; - backendContext.fQueue = queue; - backendContext.fGraphicsQueueIndex = queueIndex; - backendContext.fMaxAPIVersion = apiVersion; - backendContext.fVkExtensions = &grExtensions; - backendContext.fDeviceFeatures2 = physicalDeviceFeatures2; - backendContext.fGetProc = grGetProc; - backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo; - backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived - backendContext.fDeviceLostProc = onVkDeviceFault; - return backendContext; - }; - - VkSemaphore createExportableSemaphore() { - VkExportSemaphoreCreateInfo exportInfo; - exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; - exportInfo.pNext = nullptr; - exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - - VkSemaphoreCreateInfo semaphoreInfo; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphoreInfo.pNext = &exportInfo; - semaphoreInfo.flags = 0; - - VkSemaphore semaphore; - VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore); - if (VK_SUCCESS != err) { - ALOGE("%s: failed to create semaphore. err %d\n", __func__, err); - return VK_NULL_HANDLE; - } - - return semaphore; - } - - // syncFd cannot be <= 0 - VkSemaphore importSemaphoreFromSyncFd(int syncFd) { - VkSemaphoreCreateInfo semaphoreInfo; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphoreInfo.pNext = nullptr; - semaphoreInfo.flags = 0; - - VkSemaphore semaphore; - VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore); - if (VK_SUCCESS != err) { - ALOGE("%s: failed to create import semaphore", __func__); - return VK_NULL_HANDLE; - } - - VkImportSemaphoreFdInfoKHR importInfo; - importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR; - importInfo.pNext = nullptr; - importInfo.semaphore = semaphore; - importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT; - importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - importInfo.fd = syncFd; - - err = funcs.vkImportSemaphoreFdKHR(device, &importInfo); - if (VK_SUCCESS != err) { - funcs.vkDestroySemaphore(device, semaphore, nullptr); - ALOGE("%s: failed to import semaphore", __func__); - return VK_NULL_HANDLE; - } - - return semaphore; - } - - int exportSemaphoreSyncFd(VkSemaphore semaphore) { - int res; - - VkSemaphoreGetFdInfoKHR getFdInfo; - getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; - getFdInfo.pNext = nullptr; - getFdInfo.semaphore = semaphore; - getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - - VkResult err = funcs.vkGetSemaphoreFdKHR(device, &getFdInfo, &res); - if (VK_SUCCESS != err) { - ALOGE("%s: failed to export semaphore, err: %d", __func__, err); - return -1; - } - return res; - } - - void destroySemaphore(VkSemaphore semaphore) { - funcs.vkDestroySemaphore(device, semaphore, nullptr); - } -}; - -namespace { -void onVkDeviceFault(void* callbackContext, const std::string& description, - const std::vector& addressInfos, - const std::vector& vendorInfos, - const std::vector& vendorBinaryData) { - VulkanInterface* interface = static_cast(callbackContext); - const std::string protectedStr = interface->isProtected ? "protected" : "non-protected"; - // The final crash string should contain as much differentiating info as possible, up to 1024 - // bytes. As this final message is constructed, the same information is also dumped to the logs - // but in a more verbose format. Building the crash string is unsightly, so the clearer logging - // statement is always placed first to give context. - ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str()); - std::stringstream crashMsg; - crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr; - - if (!addressInfos.empty()) { - ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size()); - crashMsg << ", " << addressInfos.size() << " address info ("; - for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) { - ALOGE(" addressType: %d", (int)addressInfo.addressType); - ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress); - ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision); - crashMsg << addressInfo.addressType << ":" - << addressInfo.reportedAddress << ":" - << addressInfo.addressPrecision << ", "; - } - crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", " - crashMsg << ")"; - } - - if (!vendorInfos.empty()) { - ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size()); - crashMsg << ", " << vendorInfos.size() << " vendor info ("; - for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) { - ALOGE(" description: %s", vendorInfo.description); - ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode); - ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData); - // Omit descriptions for individual vendor info structs in the crash string, as the - // fault code and fault data fields should be enough for clustering, and the verbosity - // isn't worth it. Additionally, vendors may just set the general description field of - // the overall fault to the description of the first element in this list, and that - // overall description will be placed at the end of the crash string. - crashMsg << vendorInfo.vendorFaultCode << ":" - << vendorInfo.vendorFaultData << ", "; - } - crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", " - crashMsg << ")"; - } - - if (!vendorBinaryData.empty()) { - // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports - ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics" - " Stack team if you observe this message).", - vendorBinaryData.size()); - crashMsg << ", " << vendorBinaryData.size() << " bytes binary"; - } - - crashMsg << "): " << description; - LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str()); -}; -} // anonymous namespace - -static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) { - if (device != VK_NULL_HANDLE) { - return vkGetDeviceProcAddr(device, proc_name); - } - return vkGetInstanceProcAddr(instance, proc_name); -}; - -#define BAIL(fmt, ...) \ - { \ - ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \ - return interface; \ - } - -#define CHECK_NONNULL(expr) \ - if ((expr) == nullptr) { \ - BAIL("[%s] null", #expr); \ - } - -#define VK_CHECK(expr) \ - if ((expr) != VK_SUCCESS) { \ - BAIL("[%s] failed. err = %d", #expr, expr); \ - return interface; \ - } - -#define VK_GET_PROC(F) \ - PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \ - CHECK_NONNULL(vk##F) -#define VK_GET_INST_PROC(instance, F) \ - PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \ - CHECK_NONNULL(vk##F) -#define VK_GET_DEV_PROC(device, F) \ - PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \ - CHECK_NONNULL(vk##F) - -VulkanInterface initVulkanInterface(bool protectedContent = false) { - const nsecs_t timeBefore = systemTime(); - VulkanInterface interface; - - VK_GET_PROC(EnumerateInstanceVersion); - uint32_t instanceVersion; - VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion)); - - if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) { - return interface; - } - - const VkApplicationInfo appInfo = { - VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0, - VK_MAKE_VERSION(1, 1, 0), - }; - - VK_GET_PROC(EnumerateInstanceExtensionProperties); - - uint32_t extensionCount = 0; - VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr)); - std::vector instanceExtensions(extensionCount); - VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, - instanceExtensions.data())); - std::vector enabledInstanceExtensionNames; - enabledInstanceExtensionNames.reserve(instanceExtensions.size()); - interface.instanceExtensionNames.reserve(instanceExtensions.size()); - for (const auto& instExt : instanceExtensions) { - enabledInstanceExtensionNames.push_back(instExt.extensionName); - interface.instanceExtensionNames.push_back(instExt.extensionName); - } - - const VkInstanceCreateInfo instanceCreateInfo = { - VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - nullptr, - 0, - &appInfo, - 0, - nullptr, - (uint32_t)enabledInstanceExtensionNames.size(), - enabledInstanceExtensionNames.data(), - }; - - VK_GET_PROC(CreateInstance); - VkInstance instance; - VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance)); - - VK_GET_INST_PROC(instance, DestroyInstance); - interface.funcs.vkDestroyInstance = vkDestroyInstance; - VK_GET_INST_PROC(instance, EnumeratePhysicalDevices); - VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties); - VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2); - VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties); - VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2); - VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2); - VK_GET_INST_PROC(instance, CreateDevice); - - uint32_t physdevCount; - VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr)); - if (physdevCount == 0) { - BAIL("Could not find any physical devices"); - } - - physdevCount = 1; - VkPhysicalDevice physicalDevice; - VkResult enumeratePhysDevsErr = - vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice); - if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) { - BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d", - enumeratePhysDevsErr); - } - - VkPhysicalDeviceProperties2 physDevProps = { - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, - 0, - {}, - }; - VkPhysicalDeviceProtectedMemoryProperties protMemProps = { - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES, - 0, - {}, - }; - - if (protectedContent) { - physDevProps.pNext = &protMemProps; - } - - vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps); - if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { - BAIL("Could not find a Vulkan 1.1+ physical device"); - } - - if (physDevProps.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) { - // TODO: b/326633110 - SkiaVK is not working correctly on swiftshader path. - BAIL("CPU implementations of Vulkan is not supported"); - } - - // Check for syncfd support. Bail if we cannot both import and export them. - VkPhysicalDeviceExternalSemaphoreInfo semInfo = { - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, - nullptr, - VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - }; - VkExternalSemaphoreProperties semProps = { - VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0, - }; - vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps); - - bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes & - VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) && - (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) && - (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) && - (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); - - if (!sufficientSemaphoreSyncFdSupport) { - BAIL("Vulkan device does not support sufficient external semaphore sync fd features. " - "exportFromImportedHandleTypes 0x%x (needed 0x%x) " - "compatibleHandleTypes 0x%x (needed 0x%x) " - "externalSemaphoreFeatures 0x%x (needed 0x%x) ", - semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - semProps.externalSemaphoreFeatures, - VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | - VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); - } else { - ALOGD("Vulkan device supports sufficient external semaphore sync fd features. " - "exportFromImportedHandleTypes 0x%x (needed 0x%x) " - "compatibleHandleTypes 0x%x (needed 0x%x) " - "externalSemaphoreFeatures 0x%x (needed 0x%x) ", - semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - semProps.externalSemaphoreFeatures, - VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | - VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); - } - - uint32_t queueCount; - vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr); - if (queueCount == 0) { - BAIL("Could not find queues for physical device"); - } - - std::vector queueProps(queueCount); - std::vector queuePriorityProps(queueCount); - VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR; - // Even though we don't yet know if the VK_EXT_global_priority extension is available, - // we can safely add the request to the pNext chain, and if the extension is not - // available, it will be ignored. - for (uint32_t i = 0; i < queueCount; ++i) { - queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT; - queuePriorityProps[i].pNext = nullptr; - queueProps[i].pNext = &queuePriorityProps[i]; - } - vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data()); - - int graphicsQueueIndex = -1; - for (uint32_t i = 0; i < queueCount; ++i) { - // Look at potential answers to the VK_EXT_global_priority query. If answers were - // provided, we may adjust the queuePriority. - if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) { - if (queuePriorityProps[i].priorities[j] > queuePriority) { - queuePriority = queuePriorityProps[i].priorities[j]; - } - } - if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) { - interface.isRealtimePriority = true; - } - graphicsQueueIndex = i; - break; - } - } - - if (graphicsQueueIndex == -1) { - BAIL("Could not find a graphics queue family"); - } - - uint32_t deviceExtensionCount; - VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, - nullptr)); - std::vector deviceExtensions(deviceExtensionCount); - VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, - deviceExtensions.data())); - - std::vector enabledDeviceExtensionNames; - enabledDeviceExtensionNames.reserve(deviceExtensions.size()); - interface.deviceExtensionNames.reserve(deviceExtensions.size()); - for (const auto& devExt : deviceExtensions) { - enabledDeviceExtensionNames.push_back(devExt.extensionName); - interface.deviceExtensionNames.push_back(devExt.extensionName); - } - - interface.grExtensions.init(sGetProc, instance, physicalDevice, - enabledInstanceExtensionNames.size(), - enabledInstanceExtensionNames.data(), - enabledDeviceExtensionNames.size(), - enabledDeviceExtensionNames.data()); - - if (!interface.grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) { - BAIL("Vulkan driver doesn't support external semaphore fd"); - } - - interface.physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2; - interface.physicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - interface.physicalDeviceFeatures2->pNext = nullptr; - - interface.samplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures; - interface.samplerYcbcrConversionFeatures->sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; - interface.samplerYcbcrConversionFeatures->pNext = nullptr; - - interface.physicalDeviceFeatures2->pNext = interface.samplerYcbcrConversionFeatures; - void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext; - - if (protectedContent) { - interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures; - interface.protectedMemoryFeatures->sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; - interface.protectedMemoryFeatures->pNext = nullptr; - *tailPnext = interface.protectedMemoryFeatures; - tailPnext = &interface.protectedMemoryFeatures->pNext; - } - - if (interface.grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) { - interface.deviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT; - interface.deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT; - interface.deviceFaultFeatures->pNext = nullptr; - *tailPnext = interface.deviceFaultFeatures; - tailPnext = &interface.deviceFaultFeatures->pNext; - } - - vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2); - // Looks like this would slow things down and we can't depend on it on all platforms - interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE; - - if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) { - BAIL("Protected memory not supported"); - } - - float queuePriorities[1] = {0.0f}; - void* queueNextPtr = nullptr; - - VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = { - VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT, - nullptr, - // If queue priority is supported, RE should always have realtime priority. - queuePriority, - }; - - if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) { - queueNextPtr = &queuePriorityCreateInfo; - } - - VkDeviceQueueCreateFlags deviceQueueCreateFlags = - (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0); - - const VkDeviceQueueCreateInfo queueInfo = { - VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - queueNextPtr, - deviceQueueCreateFlags, - (uint32_t)graphicsQueueIndex, - 1, - queuePriorities, - }; - - const VkDeviceCreateInfo deviceInfo = { - VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - interface.physicalDeviceFeatures2, - 0, - 1, - &queueInfo, - 0, - nullptr, - (uint32_t)enabledDeviceExtensionNames.size(), - enabledDeviceExtensionNames.data(), - nullptr, - }; - - ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent); - VkDevice device; - VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device)); - ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent); - - VkQueue graphicsQueue; - VK_GET_DEV_PROC(device, GetDeviceQueue2); - const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr, - deviceQueueCreateFlags, - (uint32_t)graphicsQueueIndex, 0}; - vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue); - - VK_GET_DEV_PROC(device, DeviceWaitIdle); - VK_GET_DEV_PROC(device, DestroyDevice); - interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle; - interface.funcs.vkDestroyDevice = vkDestroyDevice; - - VK_GET_DEV_PROC(device, CreateSemaphore); - VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR); - VK_GET_DEV_PROC(device, GetSemaphoreFdKHR); - VK_GET_DEV_PROC(device, DestroySemaphore); - interface.funcs.vkCreateSemaphore = vkCreateSemaphore; - interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR; - interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR; - interface.funcs.vkDestroySemaphore = vkDestroySemaphore; - - // At this point, everything's succeeded and we can continue - interface.initialized = true; - interface.instance = instance; - interface.physicalDevice = physicalDevice; - interface.device = device; - interface.queue = graphicsQueue; - interface.queueIndex = graphicsQueueIndex; - interface.apiVersion = physDevProps.properties.apiVersion; - // grExtensions already constructed - // feature pointers already constructed - interface.grGetProc = sGetProc; - interface.isProtected = protectedContent; - // funcs already initialized - - const nsecs_t timeAfter = systemTime(); - const float initTimeMs = static_cast(timeAfter - timeBefore) / 1.0E6; - ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs); - return interface; -} - -void teardownVulkanInterface(VulkanInterface* interface) { - interface->initialized = false; - - if (interface->device != VK_NULL_HANDLE) { - interface->funcs.vkDeviceWaitIdle(interface->device); - interface->funcs.vkDestroyDevice(interface->device, nullptr); - interface->device = VK_NULL_HANDLE; - } - if (interface->instance != VK_NULL_HANDLE) { - interface->funcs.vkDestroyInstance(interface->instance, nullptr); - interface->instance = VK_NULL_HANDLE; - } - - if (interface->protectedMemoryFeatures) { - delete interface->protectedMemoryFeatures; - } - - if (interface->samplerYcbcrConversionFeatures) { - delete interface->samplerYcbcrConversionFeatures; - } - - if (interface->physicalDeviceFeatures2) { - delete interface->physicalDeviceFeatures2; - } - - if (interface->deviceFaultFeatures) { - delete interface->deviceFaultFeatures; - } - - interface->samplerYcbcrConversionFeatures = nullptr; - interface->physicalDeviceFeatures2 = nullptr; - interface->protectedMemoryFeatures = nullptr; -} - -static VulkanInterface sVulkanInterface; -static VulkanInterface sProtectedContentVulkanInterface; +static skia::VulkanInterface sVulkanInterface; +static skia::VulkanInterface sProtectedContentVulkanInterface; static void sSetupVulkanInterface() { - if (!sVulkanInterface.initialized) { - sVulkanInterface = initVulkanInterface(false /* no protected content */); + if (!sVulkanInterface.isInitialized()) { + sVulkanInterface.init(false /* no protected content */); // We will have to abort if non-protected VkDevice creation fails (then nothing works). - LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized, + LOG_ALWAYS_FATAL_IF(!sVulkanInterface.isInitialized(), "Could not initialize Vulkan RenderEngine!"); } - if (!sProtectedContentVulkanInterface.initialized) { - sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */); - if (!sProtectedContentVulkanInterface.initialized) { + if (!sProtectedContentVulkanInterface.isInitialized()) { + sProtectedContentVulkanInterface.init(true /* protected content */); + if (!sProtectedContentVulkanInterface.isInitialized()) { ALOGE("Could not initialize protected content Vulkan RenderEngine."); } } @@ -667,12 +64,12 @@ bool RenderEngine::canSupport(GraphicsApi graphicsApi) { case GraphicsApi::GL: return true; case GraphicsApi::VK: { - if (!sVulkanInterface.initialized) { - sVulkanInterface = initVulkanInterface(false /* no protected content */); + if (!sVulkanInterface.isInitialized()) { + sVulkanInterface.init(false /* no protected content */); ALOGD("%s: initialized == %s.", __func__, - sVulkanInterface.initialized ? "true" : "false"); + sVulkanInterface.isInitialized() ? "true" : "false"); } - return sVulkanInterface.initialized; + return sVulkanInterface.isInitialized(); } } } @@ -686,7 +83,7 @@ std::unique_ptr SkiaVkRenderEngine::create( std::unique_ptr engine(new SkiaVkRenderEngine(args)); engine->ensureGrContextsCreated(); - if (sVulkanInterface.initialized) { + if (sVulkanInterface.isInitialized()) { ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__); return engine; } else { @@ -721,29 +118,17 @@ SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts( } bool SkiaVkRenderEngine::supportsProtectedContentImpl() const { - return sProtectedContentVulkanInterface.initialized; + return sProtectedContentVulkanInterface.isInitialized(); } bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) { return true; } -static void delete_semaphore(void* semaphore) { - DestroySemaphoreInfo* info = reinterpret_cast(semaphore); - --info->mRefs; - if (!info->mRefs) { - sVulkanInterface.destroySemaphore(info->mSemaphore); - delete info; - } -} - -static void delete_semaphore_protected(void* semaphore) { - DestroySemaphoreInfo* info = reinterpret_cast(semaphore); - --info->mRefs; - if (!info->mRefs) { - sProtectedContentVulkanInterface.destroySemaphore(info->mSemaphore); - delete info; - } +static void unref_semaphore(void* semaphore) { + SkiaVkRenderEngine::DestroySemaphoreInfo* info = + reinterpret_cast(semaphore); + info->unref(); } static VulkanInterface& getVulkanInterface(bool protectedContext) { @@ -781,10 +166,10 @@ base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { GrFlushInfo flushInfo; DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; if (semaphore != VK_NULL_HANDLE) { - destroySemaphoreInfo = new DestroySemaphoreInfo(semaphore); + destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore); flushInfo.fNumSemaphores = 1; flushInfo.fSignalSemaphores = &backendSemaphore; - flushInfo.fFinishedProc = isProtected() ? delete_semaphore_protected : delete_semaphore; + flushInfo.fFinishedProc = unref_semaphore; flushInfo.fFinishedContext = destroySemaphoreInfo; } GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); @@ -804,7 +189,7 @@ base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { int SkiaVkRenderEngine::getContextPriority() { // EGL_CONTEXT_PRIORITY_REALTIME_NV constexpr int kRealtimePriority = 0x3357; - if (getVulkanInterface(isProtected()).isRealtimePriority) { + if (getVulkanInterface(isProtected()).isRealtimePriority()) { return kRealtimePriority; } else { return 0; @@ -813,21 +198,21 @@ int SkiaVkRenderEngine::getContextPriority() { void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) { StringAppendF(&result, "\n ------------RE Vulkan----------\n"); - StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.initialized); + StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.isInitialized()); StringAppendF(&result, "\n Vulkan protected device initialized: %d\n", - sProtectedContentVulkanInterface.initialized); + sProtectedContentVulkanInterface.isInitialized()); - if (!sVulkanInterface.initialized) { + if (!sVulkanInterface.isInitialized()) { return; } StringAppendF(&result, "\n Instance extensions:\n"); - for (const auto& name : sVulkanInterface.instanceExtensionNames) { + for (const auto& name : sVulkanInterface.getInstanceExtensionNames()) { StringAppendF(&result, "\n %s\n", name.c_str()); } StringAppendF(&result, "\n Device extensions:\n"); - for (const auto& name : sVulkanInterface.deviceExtensionNames) { + for (const auto& name : sVulkanInterface.getDeviceExtensionNames()) { StringAppendF(&result, "\n %s\n", name.c_str()); } } diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h index 52bc500a5a..ca0dcbf4ae 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.h +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -20,6 +20,7 @@ #include #include "SkiaRenderEngine.h" +#include "VulkanInterface.h" namespace android { namespace renderengine { @@ -32,6 +33,42 @@ public: int getContextPriority() override; + class DestroySemaphoreInfo { + public: + DestroySemaphoreInfo() = delete; + DestroySemaphoreInfo(const DestroySemaphoreInfo&) = delete; + DestroySemaphoreInfo& operator=(const DestroySemaphoreInfo&) = delete; + DestroySemaphoreInfo& operator=(DestroySemaphoreInfo&&) = delete; + + DestroySemaphoreInfo(VulkanInterface& vulkanInterface, std::vector semaphores) + : mVulkanInterface(vulkanInterface), mSemaphores(std::move(semaphores)) {} + DestroySemaphoreInfo(VulkanInterface& vulkanInterface, VkSemaphore semaphore) + : DestroySemaphoreInfo(vulkanInterface, std::vector(1, semaphore)) {} + + void unref() { + --mRefs; + if (!mRefs) { + for (VkSemaphore semaphore : mSemaphores) { + mVulkanInterface.destroySemaphore(semaphore); + } + delete this; + } + } + + private: + ~DestroySemaphoreInfo() = default; + + VulkanInterface& mVulkanInterface; + std::vector mSemaphores; + // We need to make sure we don't delete the VkSemaphore until it is done being used by both + // Skia (including by the GPU) and inside SkiaVkRenderEngine. So we always start with two + // refs, one owned by Skia and one owned by the SkiaVkRenderEngine. The refs are decremented + // each time unref() is called on this object. Skia will call unref() once it is done with + // the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine calls + // unref() after sending the semaphore to Skia and exporting it if need be. + int mRefs = 2; + }; + protected: // Implementations of abstract SkiaRenderEngine functions specific to // rendering backend diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp new file mode 100644 index 0000000000..453cdc1196 --- /dev/null +++ b/libs/renderengine/skia/VulkanInterface.cpp @@ -0,0 +1,582 @@ +/* + * Copyright 2024 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. + */ + +#undef LOG_TAG +#define LOG_TAG "RenderEngine" + +#include "VulkanInterface.h" + +#include +#include +#include + +#include +#include + +namespace android { +namespace renderengine { +namespace skia { + +GrVkBackendContext VulkanInterface::getBackendContext() { + GrVkBackendContext backendContext; + backendContext.fInstance = mInstance; + backendContext.fPhysicalDevice = mPhysicalDevice; + backendContext.fDevice = mDevice; + backendContext.fQueue = mQueue; + backendContext.fGraphicsQueueIndex = mQueueIndex; + backendContext.fMaxAPIVersion = mApiVersion; + backendContext.fVkExtensions = &mGrExtensions; + backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2; + backendContext.fGetProc = mGrGetProc; + backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo; + backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived + backendContext.fDeviceLostProc = onVkDeviceFault; + return backendContext; +}; + +VkSemaphore VulkanInterface::createExportableSemaphore() { + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = &exportInfo; + semaphoreInfo.flags = 0; + + VkSemaphore semaphore; + VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + if (VK_SUCCESS != err) { + ALOGE("%s: failed to create semaphore. err %d\n", __func__, err); + return VK_NULL_HANDLE; + } + + return semaphore; +} + +// syncFd cannot be <= 0 +VkSemaphore VulkanInterface::importSemaphoreFromSyncFd(int syncFd) { + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = nullptr; + semaphoreInfo.flags = 0; + + VkSemaphore semaphore; + VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + if (VK_SUCCESS != err) { + ALOGE("%s: failed to create import semaphore", __func__); + return VK_NULL_HANDLE; + } + + VkImportSemaphoreFdInfoKHR importInfo; + importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR; + importInfo.pNext = nullptr; + importInfo.semaphore = semaphore; + importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT; + importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + importInfo.fd = syncFd; + + err = mFuncs.vkImportSemaphoreFdKHR(mDevice, &importInfo); + if (VK_SUCCESS != err) { + mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr); + ALOGE("%s: failed to import semaphore", __func__); + return VK_NULL_HANDLE; + } + + return semaphore; +} + +int VulkanInterface::exportSemaphoreSyncFd(VkSemaphore semaphore) { + int res; + + VkSemaphoreGetFdInfoKHR getFdInfo; + getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + getFdInfo.pNext = nullptr; + getFdInfo.semaphore = semaphore; + getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + VkResult err = mFuncs.vkGetSemaphoreFdKHR(mDevice, &getFdInfo, &res); + if (VK_SUCCESS != err) { + ALOGE("%s: failed to export semaphore, err: %d", __func__, err); + return -1; + } + return res; +} + +void VulkanInterface::destroySemaphore(VkSemaphore semaphore) { + mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr); +} + +void VulkanInterface::onVkDeviceFault(void* callbackContext, const std::string& description, + const std::vector& addressInfos, + const std::vector& vendorInfos, + const std::vector& vendorBinaryData) { + VulkanInterface* interface = static_cast(callbackContext); + const std::string protectedStr = interface->mIsProtected ? "protected" : "non-protected"; + // The final crash string should contain as much differentiating info as possible, up to 1024 + // bytes. As this final message is constructed, the same information is also dumped to the logs + // but in a more verbose format. Building the crash string is unsightly, so the clearer logging + // statement is always placed first to give context. + ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str()); + std::stringstream crashMsg; + crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr; + + if (!addressInfos.empty()) { + ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size()); + crashMsg << ", " << addressInfos.size() << " address info ("; + for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) { + ALOGE(" addressType: %d", (int)addressInfo.addressType); + ALOGE(" reportedAddress: %" PRIu64, addressInfo.reportedAddress); + ALOGE(" addressPrecision: %" PRIu64, addressInfo.addressPrecision); + crashMsg << addressInfo.addressType << ":" << addressInfo.reportedAddress << ":" + << addressInfo.addressPrecision << ", "; + } + crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", " + crashMsg << ")"; + } + + if (!vendorInfos.empty()) { + ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size()); + crashMsg << ", " << vendorInfos.size() << " vendor info ("; + for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) { + ALOGE(" description: %s", vendorInfo.description); + ALOGE(" vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode); + ALOGE(" vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData); + // Omit descriptions for individual vendor info structs in the crash string, as the + // fault code and fault data fields should be enough for clustering, and the verbosity + // isn't worth it. Additionally, vendors may just set the general description field of + // the overall fault to the description of the first element in this list, and that + // overall description will be placed at the end of the crash string. + crashMsg << vendorInfo.vendorFaultCode << ":" << vendorInfo.vendorFaultData << ", "; + } + crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", " + crashMsg << ")"; + } + + if (!vendorBinaryData.empty()) { + // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports + ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics" + " Stack team if you observe this message).", + vendorBinaryData.size()); + crashMsg << ", " << vendorBinaryData.size() << " bytes binary"; + } + + crashMsg << "): " << description; + LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str()); +}; + +static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); +}; + +#define BAIL(fmt, ...) \ + { \ + ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \ + return; \ + } + +#define CHECK_NONNULL(expr) \ + if ((expr) == nullptr) { \ + BAIL("[%s] null", #expr); \ + } + +#define VK_CHECK(expr) \ + if ((expr) != VK_SUCCESS) { \ + BAIL("[%s] failed. err = %d", #expr, expr); \ + return; \ + } + +#define VK_GET_PROC(F) \ + PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \ + CHECK_NONNULL(vk##F) +#define VK_GET_INST_PROC(instance, F) \ + PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \ + CHECK_NONNULL(vk##F) +#define VK_GET_DEV_PROC(device, F) \ + PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \ + CHECK_NONNULL(vk##F) + +void VulkanInterface::init(bool protectedContent) { + if (isInitialized()) { + ALOGW("Called init on already initialized VulkanInterface"); + return; + } + + const nsecs_t timeBefore = systemTime(); + + VK_GET_PROC(EnumerateInstanceVersion); + uint32_t instanceVersion; + VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion)); + + if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) { + return; + } + + const VkApplicationInfo appInfo = { + VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0, + VK_MAKE_VERSION(1, 1, 0), + }; + + VK_GET_PROC(EnumerateInstanceExtensionProperties); + + uint32_t extensionCount = 0; + VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr)); + std::vector instanceExtensions(extensionCount); + VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, + instanceExtensions.data())); + std::vector enabledInstanceExtensionNames; + enabledInstanceExtensionNames.reserve(instanceExtensions.size()); + mInstanceExtensionNames.reserve(instanceExtensions.size()); + for (const auto& instExt : instanceExtensions) { + enabledInstanceExtensionNames.push_back(instExt.extensionName); + mInstanceExtensionNames.push_back(instExt.extensionName); + } + + const VkInstanceCreateInfo instanceCreateInfo = { + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + nullptr, + 0, + &appInfo, + 0, + nullptr, + (uint32_t)enabledInstanceExtensionNames.size(), + enabledInstanceExtensionNames.data(), + }; + + VK_GET_PROC(CreateInstance); + VkInstance instance; + VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance)); + + VK_GET_INST_PROC(instance, DestroyInstance); + mFuncs.vkDestroyInstance = vkDestroyInstance; + VK_GET_INST_PROC(instance, EnumeratePhysicalDevices); + VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties); + VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2); + VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties); + VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2); + VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2); + VK_GET_INST_PROC(instance, CreateDevice); + + uint32_t physdevCount; + VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr)); + if (physdevCount == 0) { + BAIL("Could not find any physical devices"); + } + + physdevCount = 1; + VkPhysicalDevice physicalDevice; + VkResult enumeratePhysDevsErr = + vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice); + if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) { + BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d", + enumeratePhysDevsErr); + } + + VkPhysicalDeviceProperties2 physDevProps = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + 0, + {}, + }; + VkPhysicalDeviceProtectedMemoryProperties protMemProps = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES, + 0, + {}, + }; + + if (protectedContent) { + physDevProps.pNext = &protMemProps; + } + + vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps); + if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { + BAIL("Could not find a Vulkan 1.1+ physical device"); + } + + if (physDevProps.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) { + // TODO: b/326633110 - SkiaVK is not working correctly on swiftshader path. + BAIL("CPU implementations of Vulkan is not supported"); + } + + // Check for syncfd support. Bail if we cannot both import and export them. + VkPhysicalDeviceExternalSemaphoreInfo semInfo = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO, + nullptr, + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + }; + VkExternalSemaphoreProperties semProps = { + VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0, + }; + vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps); + + bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes & + VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) && + (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) && + (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) && + (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); + + if (!sufficientSemaphoreSyncFdSupport) { + BAIL("Vulkan device does not support sufficient external semaphore sync fd features. " + "exportFromImportedHandleTypes 0x%x (needed 0x%x) " + "compatibleHandleTypes 0x%x (needed 0x%x) " + "externalSemaphoreFeatures 0x%x (needed 0x%x) ", + semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.externalSemaphoreFeatures, + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); + } else { + ALOGD("Vulkan device supports sufficient external semaphore sync fd features. " + "exportFromImportedHandleTypes 0x%x (needed 0x%x) " + "compatibleHandleTypes 0x%x (needed 0x%x) " + "externalSemaphoreFeatures 0x%x (needed 0x%x) ", + semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + semProps.externalSemaphoreFeatures, + VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | + VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT); + } + + uint32_t queueCount; + vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr); + if (queueCount == 0) { + BAIL("Could not find queues for physical device"); + } + + std::vector queueProps(queueCount); + std::vector queuePriorityProps(queueCount); + VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR; + // Even though we don't yet know if the VK_EXT_global_priority extension is available, + // we can safely add the request to the pNext chain, and if the extension is not + // available, it will be ignored. + for (uint32_t i = 0; i < queueCount; ++i) { + queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT; + queuePriorityProps[i].pNext = nullptr; + queueProps[i].pNext = &queuePriorityProps[i]; + } + vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data()); + + int graphicsQueueIndex = -1; + for (uint32_t i = 0; i < queueCount; ++i) { + // Look at potential answers to the VK_EXT_global_priority query. If answers were + // provided, we may adjust the queuePriority. + if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) { + if (queuePriorityProps[i].priorities[j] > queuePriority) { + queuePriority = queuePriorityProps[i].priorities[j]; + } + } + if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) { + mIsRealtimePriority = true; + } + graphicsQueueIndex = i; + break; + } + } + + if (graphicsQueueIndex == -1) { + BAIL("Could not find a graphics queue family"); + } + + uint32_t deviceExtensionCount; + VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, + nullptr)); + std::vector deviceExtensions(deviceExtensionCount); + VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount, + deviceExtensions.data())); + + std::vector enabledDeviceExtensionNames; + enabledDeviceExtensionNames.reserve(deviceExtensions.size()); + mDeviceExtensionNames.reserve(deviceExtensions.size()); + for (const auto& devExt : deviceExtensions) { + enabledDeviceExtensionNames.push_back(devExt.extensionName); + mDeviceExtensionNames.push_back(devExt.extensionName); + } + + mGrExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(), + enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(), + enabledDeviceExtensionNames.data()); + + if (!mGrExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) { + BAIL("Vulkan driver doesn't support external semaphore fd"); + } + + mPhysicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2; + mPhysicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + mPhysicalDeviceFeatures2->pNext = nullptr; + + mSamplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures; + mSamplerYcbcrConversionFeatures->sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + mSamplerYcbcrConversionFeatures->pNext = nullptr; + + mPhysicalDeviceFeatures2->pNext = mSamplerYcbcrConversionFeatures; + void** tailPnext = &mSamplerYcbcrConversionFeatures->pNext; + + if (protectedContent) { + mProtectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures; + mProtectedMemoryFeatures->sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + mProtectedMemoryFeatures->pNext = nullptr; + *tailPnext = mProtectedMemoryFeatures; + tailPnext = &mProtectedMemoryFeatures->pNext; + } + + if (mGrExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) { + mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT; + mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT; + mDeviceFaultFeatures->pNext = nullptr; + *tailPnext = mDeviceFaultFeatures; + tailPnext = &mDeviceFaultFeatures->pNext; + } + + vkGetPhysicalDeviceFeatures2(physicalDevice, mPhysicalDeviceFeatures2); + // Looks like this would slow things down and we can't depend on it on all platforms + mPhysicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE; + + if (protectedContent && !mProtectedMemoryFeatures->protectedMemory) { + BAIL("Protected memory not supported"); + } + + float queuePriorities[1] = {0.0f}; + void* queueNextPtr = nullptr; + + VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT, + nullptr, + // If queue priority is supported, RE should always have realtime priority. + queuePriority, + }; + + if (mGrExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) { + queueNextPtr = &queuePriorityCreateInfo; + } + + VkDeviceQueueCreateFlags deviceQueueCreateFlags = + (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0); + + const VkDeviceQueueCreateInfo queueInfo = { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + queueNextPtr, + deviceQueueCreateFlags, + (uint32_t)graphicsQueueIndex, + 1, + queuePriorities, + }; + + const VkDeviceCreateInfo deviceInfo = { + VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + mPhysicalDeviceFeatures2, + 0, + 1, + &queueInfo, + 0, + nullptr, + (uint32_t)enabledDeviceExtensionNames.size(), + enabledDeviceExtensionNames.data(), + nullptr, + }; + + ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent); + VkDevice device; + VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device)); + ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent); + + VkQueue graphicsQueue; + VK_GET_DEV_PROC(device, GetDeviceQueue2); + const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr, + deviceQueueCreateFlags, + (uint32_t)graphicsQueueIndex, 0}; + vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue); + + VK_GET_DEV_PROC(device, DeviceWaitIdle); + VK_GET_DEV_PROC(device, DestroyDevice); + mFuncs.vkDeviceWaitIdle = vkDeviceWaitIdle; + mFuncs.vkDestroyDevice = vkDestroyDevice; + + VK_GET_DEV_PROC(device, CreateSemaphore); + VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR); + VK_GET_DEV_PROC(device, GetSemaphoreFdKHR); + VK_GET_DEV_PROC(device, DestroySemaphore); + mFuncs.vkCreateSemaphore = vkCreateSemaphore; + mFuncs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR; + mFuncs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR; + mFuncs.vkDestroySemaphore = vkDestroySemaphore; + + // At this point, everything's succeeded and we can continue + mInitialized = true; + mInstance = instance; + mPhysicalDevice = physicalDevice; + mDevice = device; + mQueue = graphicsQueue; + mQueueIndex = graphicsQueueIndex; + mApiVersion = physDevProps.properties.apiVersion; + // grExtensions already constructed + // feature pointers already constructed + mGrGetProc = sGetProc; + mIsProtected = protectedContent; + // mIsRealtimePriority already initialized by constructor + // funcs already initialized + + const nsecs_t timeAfter = systemTime(); + const float initTimeMs = static_cast(timeAfter - timeBefore) / 1.0E6; + ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs); +} + +// TODO: b/293371537 - Iterate on this. +// Currently unused, but copied over from its original location for potential future use. This +// should likely be improved to walk the pNext chain of mPhysicalDeviceFeatures2 and free everything +// like HWUI's VulkanManager. Also, not all fields are being reset. +void VulkanInterface::teardown() { + mInitialized = false; + + if (mDevice != VK_NULL_HANDLE) { + mFuncs.vkDeviceWaitIdle(mDevice); + mFuncs.vkDestroyDevice(mDevice, nullptr); + mDevice = VK_NULL_HANDLE; + } + if (mInstance != VK_NULL_HANDLE) { + mFuncs.vkDestroyInstance(mInstance, nullptr); + mInstance = VK_NULL_HANDLE; + } + + if (mProtectedMemoryFeatures) { + delete mProtectedMemoryFeatures; + } + + if (mSamplerYcbcrConversionFeatures) { + delete mSamplerYcbcrConversionFeatures; + } + + if (mPhysicalDeviceFeatures2) { + delete mPhysicalDeviceFeatures2; + } + + if (mDeviceFaultFeatures) { + delete mDeviceFaultFeatures; + } + + mSamplerYcbcrConversionFeatures = nullptr; + mPhysicalDeviceFeatures2 = nullptr; + mProtectedMemoryFeatures = nullptr; + mDeviceFaultFeatures = nullptr; +} + +} // namespace skia +} // namespace renderengine +} // namespace android diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h new file mode 100644 index 0000000000..c3936d9869 --- /dev/null +++ b/libs/renderengine/skia/VulkanInterface.h @@ -0,0 +1,95 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +using namespace skgpu; + +namespace android { +namespace renderengine { +namespace skia { + +class VulkanInterface { +public: + // Create an uninitialized interface. Initialize with `init`. + VulkanInterface() = default; + ~VulkanInterface() = default; + VulkanInterface(const VulkanInterface&) = delete; + VulkanInterface& operator=(const VulkanInterface&) = delete; + VulkanInterface& operator=(VulkanInterface&&) = delete; + + void init(bool protectedContent = false); + void teardown(); + + // TODO: b/293371537 - Graphite variant (external/skia/include/gpu/vk/VulkanBackendContext.h) + GrVkBackendContext getBackendContext(); + VkSemaphore createExportableSemaphore(); + VkSemaphore importSemaphoreFromSyncFd(int syncFd); + int exportSemaphoreSyncFd(VkSemaphore semaphore); + void destroySemaphore(VkSemaphore semaphore); + + bool isInitialized() const { return mInitialized; } + bool isRealtimePriority() const { return mIsRealtimePriority; } + const std::vector& getInstanceExtensionNames() { return mInstanceExtensionNames; } + const std::vector& getDeviceExtensionNames() { return mDeviceExtensionNames; } + +private: + struct VulkanFuncs { + PFN_vkCreateSemaphore vkCreateSemaphore = nullptr; + PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr; + PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr; + PFN_vkDestroySemaphore vkDestroySemaphore = nullptr; + + PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr; + PFN_vkDestroyDevice vkDestroyDevice = nullptr; + PFN_vkDestroyInstance vkDestroyInstance = nullptr; + }; + + static void onVkDeviceFault(void* callbackContext, const std::string& description, + const std::vector& addressInfos, + const std::vector& vendorInfos, + const std::vector& vendorBinaryData); + + bool mInitialized = false; + VkInstance mInstance = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mDevice = VK_NULL_HANDLE; + VkQueue mQueue = VK_NULL_HANDLE; + int mQueueIndex = 0; + uint32_t mApiVersion = 0; + GrVkExtensions mGrExtensions; + VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr; + VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr; + VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr; + VkPhysicalDeviceFaultFeaturesEXT* mDeviceFaultFeatures = nullptr; + GrVkGetProc mGrGetProc = nullptr; + bool mIsProtected = false; + bool mIsRealtimePriority = false; + + VulkanFuncs mFuncs; + + std::vector mInstanceExtensionNames; + std::vector mDeviceExtensionNames; +}; + +} // namespace skia +} // namespace renderengine +} // namespace android -- GitLab From 0f00ad9cf9d5353df0cbc1e57408d0131f35fe8d Mon Sep 17 00:00:00 2001 From: Cody Heiner Date: Tue, 13 Feb 2024 14:19:25 -0800 Subject: [PATCH 046/465] Remove build dependency of surfaceflinger on libinput frameworks/native/libs/input: - Extract commonly used input aidl files to a static library `iinputflinger_aidl_lib_static`. - Update `libinput` to use this library instead of aidl sources. frameworks/native/services/surfaceflinger: - Replace surfaceflinger dependency on `libinput` with `iinputflinger_aidl_lib_static`. vendor/google_arc/libs/libsurfaceflinger_arc: - We have to add back the dependency on `libinput` here, since surfaceflinger_arc depends on `libinput_serialtracker`, which uses libinput definitions. Note: We may want to try to remove the surfaceflinger_arc dependency on libinput in a follow-up change. Test: m checkinput Test: m surfaceflinger && m libsurfaceflinger_arc_test && m surfaceflinger_service_fuzzer Test: Flash to device (tangorpro) and device boots normally. Test: Input functions normally (manual test of stylus prediction shows MotionPredictor working and prediction metrics to be reasonable). Bug: 325334079 Change-Id: Id549d0f6c4f3cd85d4cba5d6f8f969327f7b6de1 --- libs/input/Android.bp | 36 ++++++++++++++++++++---------- services/surfaceflinger/Android.bp | 2 +- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 8b693390d0..f4c32f2aaa 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -135,6 +135,29 @@ rust_bindgen { ], } +cc_library_static { + name: "iinputflinger_aidl_lib_static", + host_supported: true, + srcs: [ + "android/os/IInputFlinger.aidl", + "android/os/InputChannelCore.aidl", + ], + shared_libs: [ + "libbinder", + ], + whole_static_libs: [ + "libgui_window_info_static", + ], + aidl: { + export_aidl_headers: true, + local_include_dirs: ["."], + include_dirs: [ + "frameworks/native/libs/gui", + "frameworks/native/libs/input", + ], + }, +} + // Contains methods to help access C++ code from rust cc_library_static { name: "libinput_from_rust_to_cpp", @@ -175,8 +198,6 @@ cc_library { "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION", ], srcs: [ - "android/os/IInputFlinger.aidl", - "android/os/InputChannelCore.aidl", "AccelerationCurve.cpp", "Input.cpp", "InputDevice.cpp", @@ -239,7 +260,6 @@ cc_library { static_libs: [ "inputconstants-cpp", - "libgui_window_info_static", "libui-types", "libtflite_static", "libkernelconfigs", @@ -248,10 +268,10 @@ cc_library { whole_static_libs: [ "com.android.input.flags-aconfig-cc", "libinput_rust_ffi", + "iinputflinger_aidl_lib_static", ], export_static_lib_headers: [ - "libgui_window_info_static", "libui-types", ], @@ -285,14 +305,6 @@ cc_library { ], }, }, - - aidl: { - local_include_dirs: ["."], - export_aidl_headers: true, - include_dirs: [ - "frameworks/native/libs/gui", - ], - }, } // Use bootstrap version of stats logging library. diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index d77180362b..0638e022db 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -83,11 +83,11 @@ cc_defaults { "libprotobuf-cpp-lite", "libsync", "libui", - "libinput", "libutils", "libSurfaceFlingerProp", ], static_libs: [ + "iinputflinger_aidl_lib_static", "libaidlcommonsupport", "libcompositionengine", "libframetimeline", -- GitLab From e77016441606674de6d9445d6e6a65ee90a50221 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 22 Feb 2024 04:06:53 +0000 Subject: [PATCH 047/465] InputTracer: Consolidate logic for marking event processing complete Bug: 210460522 Test: atest inputflinger_tests Change-Id: I2288565088dfd477e063c78be450699b258321a3 --- .../dispatcher/trace/InputTracer.cpp | 24 ++++++++++--------- .../dispatcher/trace/InputTracer.h | 2 ++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 3b5a09694c..d16cbf7f2f 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -111,11 +111,7 @@ void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { LOG(FATAL) << "Traced event was already logged. " "eventProcessingComplete() was likely called more than once."; } - - for (const auto& event : eventState->events) { - writeEventToBackend(event, *mBackend); - } - eventState->isEventProcessingComplete = true; + eventState->onEventProcessingComplete(); } std::unique_ptr InputTracer::traceDerivedEvent( @@ -189,18 +185,24 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { // --- InputTracer::EventState --- +void InputTracer::EventState::onEventProcessingComplete() { + // Write all of the events known so far to the trace. + for (const auto& event : events) { + writeEventToBackend(event, *tracer.mBackend); + } + isEventProcessingComplete = true; +} + InputTracer::EventState::~EventState() { if (isEventProcessingComplete) { // This event has already been written to the trace as expected. return; } // The event processing was never marked as complete, so do it now. - // TODO(b/210460522): Determine why/where the event is being destroyed before - // eventProcessingComplete() is called. - for (const auto& event : events) { - writeEventToBackend(event, *tracer.mBackend); - } - isEventProcessingComplete = true; + // We should never end up here in normal operation. However, in tests, it's possible that we + // stop and destroy InputDispatcher without waiting for it to finish processing events, at + // which point an event (and thus its EventState) may be destroyed before processing finishes. + onEventProcessingComplete(); } } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index ccff30e59d..25a5651393 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -56,6 +56,8 @@ private: explicit inline EventState(InputTracer& tracer) : tracer(tracer){}; ~EventState(); + void onEventProcessingComplete(); + InputTracer& tracer; std::vector events; bool isEventProcessingComplete{false}; -- GitLab From d6b2b05f75fc82bd9817878d0feb2a6f425dc57d Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 21 Feb 2024 23:25:15 +0000 Subject: [PATCH 048/465] InputTracer: Create tracker for tracing synthetic events Allow the creation of a trace tracker for a synthetic event that does not stem from an inbound input event. This is used for any dispatching cycle that has a non-input event root, such as ANR timers, window removals, API interactions (e.g. pilfer pointers), etc. Any key or motion events generated for this synthetic event should be traced as a derived event. We achieve this by passing the trace tracker through the dispatching pipeline, and tracing all of the synthesized events for that root using the same tracker. Since all synthetic events can now be traced, we can now enforce that all dispatched events have been previously traced as either an inbound or derived event. This makes the event cookie non-nullable. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I3417aee300edc251e2f7cb76c1f719502a5f5b8b --- .../dispatcher/CancelationOptions.h | 10 +- .../dispatcher/InputDispatcher.cpp | 135 +++++++++++++----- .../inputflinger/dispatcher/InputDispatcher.h | 8 +- .../dispatcher/trace/InputTracer.cpp | 23 ++- .../dispatcher/trace/InputTracer.h | 3 +- .../dispatcher/trace/InputTracerInterface.h | 17 ++- 6 files changed, 152 insertions(+), 44 deletions(-) diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index 83e6a60602..9c73f03dc8 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -16,6 +16,8 @@ #pragma once +#include "trace/EventTrackerInterface.h" + #include #include #include @@ -51,7 +53,13 @@ struct CancelationOptions { // The specific pointers to cancel, or nullopt to cancel all pointer events std::optional> pointerIds = std::nullopt; - CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {} + const std::unique_ptr& traceTracker; + + explicit CancelationOptions(Mode mode, const char* reason, + const std::unique_ptr& traceTracker) + : mode(mode), reason(reason), traceTracker(traceTracker) {} + CancelationOptions(const CancelationOptions&) = delete; + CancelationOptions operator=(const CancelationOptions&) = delete; }; } // namespace inputdispatcher diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 56b0c8f12c..f06caa6a40 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -862,6 +862,30 @@ std::pair expandCancellatio } } +class ScopedSyntheticEventTracer { +public: + ScopedSyntheticEventTracer(std::unique_ptr& tracer) + : mTracer(tracer) { + if (mTracer) { + mEventTracker = mTracer->createTrackerForSyntheticEvent(); + } + } + + ~ScopedSyntheticEventTracer() { + if (mTracer) { + mTracer->eventProcessingComplete(*mEventTracker); + } + } + + const std::unique_ptr& getTracker() const { + return mEventTracker; + } + +private: + std::unique_ptr& mTracer; + std::unique_ptr mEventTracker; +}; + } // namespace // --- InputDispatcher --- @@ -1479,8 +1503,9 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason switch (entry.type) { case EventEntry::Type::KEY: { - CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason); const KeyEntry& keyEntry = static_cast(entry); + CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason, + keyEntry.traceTracker); options.displayId = keyEntry.displayId; options.deviceId = keyEntry.deviceId; synthesizeCancelationEventsForAllConnectionsLocked(options); @@ -1489,13 +1514,14 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason case EventEntry::Type::MOTION: { const MotionEntry& motionEntry = static_cast(entry); if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) { - CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason); + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason, + motionEntry.traceTracker); options.displayId = motionEntry.displayId; options.deviceId = motionEntry.deviceId; synthesizeCancelationEventsForAllConnectionsLocked(options); } else { CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, - reason); + reason, motionEntry.traceTracker); options.displayId = motionEntry.displayId; options.deviceId = motionEntry.deviceId; synthesizeCancelationEventsForAllConnectionsLocked(options); @@ -1630,7 +1656,9 @@ bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime, resetKeyRepeatLocked(); } - CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset"); + ScopedSyntheticEventTracer traceContext(mTracer); + CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset", + traceContext.getTracker()); options.deviceId = entry.deviceId; synthesizeCancelationEventsForAllConnectionsLocked(options); @@ -2019,7 +2047,7 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, CancelationOptions::Mode mode( isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS); - CancelationOptions options(mode, "input event injection failed"); + CancelationOptions options(mode, "input event injection failed", entry->traceTracker); options.displayId = entry->displayId; synthesizeCancelationEventsForMonitorsLocked(options); return true; @@ -2125,8 +2153,9 @@ void InputDispatcher::cancelEventsForAnrLocked(const std::shared_ptr if (connection->status != Connection::Status::NORMAL) { return; } + ScopedSyntheticEventTracer traceContext(mTracer); CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, - "application not responding"); + "application not responding", traceContext.getTracker()); sp windowHandle; if (!connection->monitor) { @@ -3521,6 +3550,10 @@ void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptrdeviceId << " in " << connection->getInputChannelName() << " with event " << cancelEvent->getDescription(); + if (mTracer) { + static_cast(*cancelEvent).traceTracker = + mTracer->traceDerivedEvent(*cancelEvent, *resolvedMotion->traceTracker); + } std::unique_ptr cancelDispatchEntry = createDispatchEntry(mIdGenerator, inputTarget, std::move(cancelEvent), ftl::Flags(), mWindowInfosVsyncId, @@ -3758,7 +3791,8 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, keyEntry.metaState, keyEntry.repeatCount, keyEntry.downTime, keyEntry.eventTime); if (mTracer) { - mTracer->traceEventDispatch(*dispatchEntry, keyEntry.traceTracker.get()); + ensureEventTraced(keyEntry); + mTracer->traceEventDispatch(*dispatchEntry, *keyEntry.traceTracker); } break; } @@ -3771,7 +3805,8 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const MotionEntry& motionEntry = static_cast(eventEntry); status = publishMotionEvent(*connection, *dispatchEntry); if (mTracer) { - mTracer->traceEventDispatch(*dispatchEntry, motionEntry.traceTracker.get()); + ensureEventTraced(motionEntry); + mTracer->traceEventDispatch(*dispatchEntry, *motionEntry.traceTracker); } break; } @@ -4150,6 +4185,11 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( switch (cancelationEventEntry->type) { case EventEntry::Type::KEY: { + if (mTracer) { + static_cast(*cancelationEventEntry).traceTracker = + mTracer->traceDerivedEvent(*cancelationEventEntry, + *options.traceTracker); + } const auto& keyEntry = static_cast(*cancelationEventEntry); if (window) { addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS, @@ -4161,6 +4201,11 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( break; } case EventEntry::Type::MOTION: { + if (mTracer) { + static_cast(*cancelationEventEntry).traceTracker = + mTracer->traceDerivedEvent(*cancelationEventEntry, + *options.traceTracker); + } const auto& motionEntry = static_cast(*cancelationEventEntry); if (window) { std::bitset pointerIds; @@ -4208,6 +4253,9 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( } if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created"; + if (mTracer) { + mTracer->dispatchToTargetHint(*options.traceTracker, targets[0]); + } enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0]); } @@ -4219,7 +4267,8 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( const nsecs_t downTime, const std::shared_ptr& connection, - ftl::Flags targetFlags) { + ftl::Flags targetFlags, + const std::unique_ptr& traceTracker) { if (connection->status != Connection::Status::NORMAL) { return; } @@ -4248,6 +4297,10 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( std::vector targets{}; switch (downEventEntry->type) { case EventEntry::Type::MOTION: { + if (mTracer) { + static_cast(*downEventEntry).traceTracker = + mTracer->traceDerivedEvent(*downEventEntry, *traceTracker); + } const auto& motionEntry = static_cast(*downEventEntry); if (windowHandle != nullptr) { std::bitset pointerIds; @@ -4285,6 +4338,9 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked( } if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created"; + if (mTracer) { + mTracer->dispatchToTargetHint(*traceTracker, targets[0]); + } enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0]); } @@ -5222,6 +5278,7 @@ void InputDispatcher::setInputWindowsLocked( } LOG(INFO) << "setInputWindows displayId=" << displayId << " " << windowList; } + ScopedSyntheticEventTracer traceContext(mTracer); // Check preconditions for new input windows for (const sp& window : windowInfoHandles) { @@ -5261,7 +5318,7 @@ void InputDispatcher::setInputWindowsLocked( std::optional changes = mFocusResolver.setInputWindows(displayId, windowHandles); if (changes) { - onFocusChangedLocked(*changes, removedFocusedWindowHandle); + onFocusChangedLocked(*changes, traceContext.getTracker(), removedFocusedWindowHandle); } std::unordered_map::iterator stateIt = @@ -5274,7 +5331,7 @@ void InputDispatcher::setInputWindowsLocked( LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName() << " in display %" << displayId; CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "touched window was removed"); + "touched window was removed", traceContext.getTracker()); synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options); // Since we are about to drop the touch, cancel the events for the wallpaper as // well. @@ -5375,6 +5432,7 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { } { // acquire lock std::scoped_lock _l(mLock); + ScopedSyntheticEventTracer traceContext(mTracer); if (mFocusedDisplayId != displayId) { sp oldFocusedWindowToken = @@ -5387,7 +5445,8 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { } CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, - "The display which contains this window no longer has focus."); + "The display which contains this window no longer has focus.", + traceContext.getTracker()); options.displayId = ADISPLAY_ID_NONE; synthesizeCancelationEventsForWindowLocked(windowHandle, options); } @@ -5612,19 +5671,22 @@ bool InputDispatcher::transferTouchGesture(const sp& fromToken, const s } // Synthesize cancel for old window and down for new window. + ScopedSyntheticEventTracer traceContext(mTracer); std::shared_ptr fromConnection = getConnectionLocked(fromToken); std::shared_ptr toConnection = getConnectionLocked(toToken); if (fromConnection != nullptr && toConnection != nullptr) { fromConnection->inputState.mergePointerStateTo(toConnection->inputState); CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "transferring touch from this window to another window"); + "transferring touch from this window to another window", + traceContext.getTracker()); synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection); synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection, - newTargetFlags); + newTargetFlags, + traceContext.getTracker()); // Check if the wallpaper window should deliver the corresponding event. transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle, - *state, deviceId, pointers); + *state, deviceId, pointers, traceContext.getTracker()); } } // release lock @@ -5692,7 +5754,9 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { ALOGD("Resetting and dropping all events (%s).", reason); } - CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason); + ScopedSyntheticEventTracer traceContext(mTracer); + CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason, + traceContext.getTracker()); synthesizeCancelationEventsForAllConnectionsLocked(options); resetKeyRepeatLocked(); @@ -6087,12 +6151,13 @@ status_t InputDispatcher::pilferPointersLocked(const sp& token) { return BAD_VALUE; } + ScopedSyntheticEventTracer traceContext(mTracer); for (const DeviceId deviceId : deviceIds) { TouchState& state = *statePtr; TouchedWindow& window = *windowPtr; // Send cancel events to all the input channels we're stealing from. CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "input channel stole pointer stream"); + "input channel stole pointer stream", traceContext.getTracker()); options.deviceId = deviceId; options.displayId = displayId; std::vector pointers = window.getTouchingPointers(deviceId); @@ -6516,7 +6581,8 @@ std::unique_ptr InputDispatcher::afterKeyEventLockedInterruptabl CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, "application handled the original non-fallback key " "or is no longer a foreground target, " - "canceling previously dispatched fallback key"); + "canceling previously dispatched fallback key", + keyEntry.traceTracker); options.keyCode = *fallbackKeyCode; synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection); } @@ -6598,7 +6664,8 @@ std::unique_ptr InputDispatcher::afterKeyEventLockedInterruptabl const auto windowHandle = getWindowHandleLocked(connection->getToken()); if (windowHandle != nullptr) { CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS, - "canceling fallback, policy no longer desires it"); + "canceling fallback, policy no longer desires it", + keyEntry.traceTracker); options.keyCode = *fallbackKeyCode; synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection); } @@ -6736,16 +6803,19 @@ void InputDispatcher::setFocusedWindow(const FocusRequest& request) { std::scoped_lock _l(mLock); std::optional changes = mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId)); + ScopedSyntheticEventTracer traceContext(mTracer); if (changes) { - onFocusChangedLocked(*changes); + onFocusChangedLocked(*changes, traceContext.getTracker()); } } // release lock // Wake up poll loop since it may need to make new input dispatching choices. mLooper->wake(); } -void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes, - const sp removedFocusedWindowHandle) { +void InputDispatcher::onFocusChangedLocked( + const FocusResolver::FocusChanges& changes, + const std::unique_ptr& traceTracker, + const sp removedFocusedWindowHandle) { if (changes.oldFocus) { const auto resolvedWindow = removedFocusedWindowHandle != nullptr ? removedFocusedWindowHandle @@ -6754,7 +6824,7 @@ void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& ch LOG(FATAL) << __func__ << ": Previously focused token did not have a window"; } CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, - "focus left window"); + "focus left window", traceTracker); synthesizeCancelationEventsForWindowLocked(resolvedWindow, options); enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason); } @@ -6907,9 +6977,10 @@ void InputDispatcher::DispatcherWindowListener::onWindowInfosChanged( void InputDispatcher::cancelCurrentTouch() { { std::scoped_lock _l(mLock); + ScopedSyntheticEventTracer traceContext(mTracer); ALOGD("Canceling all ongoing pointer gestures on all displays."); CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "cancel current touch"); + "cancel current touch", traceContext.getTracker()); synthesizeCancelationEventsForAllConnectionsLocked(options); mTouchStatesByDisplay.clear(); @@ -6959,12 +7030,12 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFl } } -void InputDispatcher::transferWallpaperTouch(ftl::Flags oldTargetFlags, - ftl::Flags newTargetFlags, - const sp fromWindowHandle, - const sp toWindowHandle, - TouchState& state, int32_t deviceId, - const std::vector& pointers) { +void InputDispatcher::transferWallpaperTouch( + ftl::Flags oldTargetFlags, + ftl::Flags newTargetFlags, const sp fromWindowHandle, + const sp toWindowHandle, TouchState& state, int32_t deviceId, + const std::vector& pointers, + const std::unique_ptr& traceTracker) { const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) && fromWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); @@ -6982,7 +7053,7 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags oldT if (oldWallpaper != nullptr) { CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "transferring touch focus to another window"); + "transferring touch focus to another window", traceTracker); state.removeWindowByToken(oldWallpaper->getToken()); synthesizeCancelationEventsForWindowLocked(oldWallpaper, options); } @@ -7002,7 +7073,7 @@ void InputDispatcher::transferWallpaperTouch(ftl::Flags oldT getConnectionLocked(toWindowHandle->getToken()); toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState); synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection, - wallpaperFlags); + wallpaperFlags, traceTracker); } } } diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 269bfddb8c..d6eba64dd0 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -628,7 +628,8 @@ private: void synthesizePointerDownEventsForConnectionLocked( const nsecs_t downTime, const std::shared_ptr& connection, - ftl::Flags targetFlags) REQUIRES(mLock); + ftl::Flags targetFlags, + const std::unique_ptr& traceTracker) REQUIRES(mLock); // Splitting motion events across windows. When splitting motion event for a target, // splitDownTime refers to the time of first 'down' event on that particular target @@ -657,6 +658,7 @@ private: void doInterceptKeyBeforeDispatchingCommand(const sp& focusedWindowToken, const KeyEntry& entry) REQUIRES(mLock); void onFocusChangedLocked(const FocusResolver::FocusChanges& changes, + const std::unique_ptr& traceTracker, const sp removedFocusedWindowHandle = nullptr) REQUIRES(mLock); void sendFocusChangedCommandLocked(const sp& oldToken, const sp& newToken) @@ -704,7 +706,9 @@ private: const sp fromWindowHandle, const sp toWindowHandle, TouchState& state, int32_t deviceId, - const std::vector& pointers) REQUIRES(mLock); + const std::vector& pointers, + const std::unique_ptr& traceTracker) + REQUIRES(mLock); sp findWallpaperWindowBelow( const sp& windowHandle) const REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index d16cbf7f2f..83ed452d6e 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -65,6 +65,10 @@ void writeEventToBackend(const TracedEvent& event, InputTracingBackendInterface& event); } +inline auto getId(const trace::TracedEvent& v) { + return std::visit([](const auto& event) { return event.id; }, v); +} + } // namespace // --- InputTracer --- @@ -89,6 +93,12 @@ std::unique_ptr InputTracer::traceInboundEvent(const Even return std::make_unique(std::move(eventState), /*isDerived=*/false); } +std::unique_ptr InputTracer::createTrackerForSyntheticEvent() { + // Create a new EventState to track events derived from this tracker. + return std::make_unique(std::make_shared(*this), + /*isDerived=*/false); +} + void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, const InputTarget& target) { if (isDerivedCookie(cookie)) { @@ -140,7 +150,8 @@ std::unique_ptr InputTracer::traceDerivedEvent( } void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, - const EventTrackerInterface* cookie) { + const EventTrackerInterface& cookie) { + auto& eventState = getState(cookie); const EventEntry& entry = *dispatchEntry.eventEntry; // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable. // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced, @@ -159,9 +170,13 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); } - if (!cookie) { - // This event was not tracked as an inbound event, so trace it now. - writeEventToBackend(traced, *mBackend); + auto tracedEventIt = + std::find_if(eventState->events.begin(), eventState->events.end(), + [&traced](const auto& event) { return getId(traced) == getId(event); }); + if (tracedEventIt == eventState->events.end()) { + LOG(FATAL) + << __func__ + << ": Failed to find a previously traced event that matches the dispatched event"; } // The vsyncId only has meaning if the event is targeting a window. diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 25a5651393..529c0fafed 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -42,11 +42,12 @@ public: InputTracer& operator=(const InputTracer&) = delete; std::unique_ptr traceInboundEvent(const EventEntry&) override; + std::unique_ptr createTrackerForSyntheticEvent() override; void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override; void eventProcessingComplete(const EventTrackerInterface&) override; std::unique_ptr traceDerivedEvent(const EventEntry&, const EventTrackerInterface&) override; - void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override; + void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override; private: std::unique_ptr mBackend; diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h index daf716f993..609d10c0f7 100644 --- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h @@ -52,6 +52,15 @@ public: * to track the event's lifecycle inside InputDispatcher. */ virtual std::unique_ptr traceInboundEvent(const EventEntry&) = 0; + + /** + * Create a trace tracker for a synthetic event that does not stem from an inbound input event. + * This includes things like generating cancellations or down events for various reasons, + * such as ANR, pilfering, transfer touch, etc. Any key or motion events generated for this + * synthetic event should be traced as a derived event using {@link #traceDerivedEvent}. + */ + virtual std::unique_ptr createTrackerForSyntheticEvent() = 0; + /** * Notify the tracer that the traced event will be sent to the given InputTarget. * The tracer may change how the event is logged depending on the target. For example, @@ -89,11 +98,11 @@ public: /** * Trace an input event being successfully dispatched to a window. The dispatched event may - * be a previously traced inbound event, or it may be a synthesized event that has not been - * previously traced. For inbound events that were previously traced, the EventTracker cookie - * must be provided. For events that were not previously traced, the cookie must be null. + * be a previously traced inbound event, or it may be a synthesized event. All dispatched events + * must have been previously traced, so the trace tracker associated with the event must be + * provided. */ - virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) = 0; + virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) = 0; }; } // namespace android::inputdispatcher::trace -- GitLab From 52ec3ff37923d8690dae479a395af73a97c3f45f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 22 Feb 2024 03:26:38 +0000 Subject: [PATCH 049/465] InputTracer: Wait to trace dispatch events to the backend ... until event processing is complete. This is because we need to wait until processing complete to get information about the targets and sensitivity of an event, which is required to write the dispatch event to the backend. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I227c207d6a5667f9297e74b4c8be9f8e980d6d13 --- .../dispatcher/trace/InputTracer.cpp | 25 ++++++++++++++++--- .../dispatcher/trace/InputTracer.h | 4 +++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 83ed452d6e..49e6e21828 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -183,10 +183,21 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, const int32_t windowId = dispatchEntry.windowId.value_or(0); const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0; - mBackend->traceWindowDispatch({std::move(traced), dispatchEntry.deliveryTime, - dispatchEntry.resolvedFlags, dispatchEntry.targetUid, vsyncId, - windowId, dispatchEntry.transform, dispatchEntry.rawTransform, - /*hmac=*/{}, resolvedKeyRepeatCount}); + const WindowDispatchArgs windowDispatchArgs{std::move(traced), + dispatchEntry.deliveryTime, + dispatchEntry.resolvedFlags, + dispatchEntry.targetUid, + vsyncId, + windowId, + dispatchEntry.transform, + dispatchEntry.rawTransform, + /*hmac=*/{}, + resolvedKeyRepeatCount}; + if (eventState->isEventProcessingComplete) { + mBackend->traceWindowDispatch(std::move(windowDispatchArgs)); + } else { + eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs)); + } } std::shared_ptr& InputTracer::getState( @@ -205,6 +216,12 @@ void InputTracer::EventState::onEventProcessingComplete() { for (const auto& event : events) { writeEventToBackend(event, *tracer.mBackend); } + // Write all pending dispatch args to the trace. + for (const auto& windowDispatchArgs : pendingDispatchArgs) { + tracer.mBackend->traceWindowDispatch(windowDispatchArgs); + } + pendingDispatchArgs.clear(); + isEventProcessingComplete = true; } diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 529c0fafed..8da9632d7c 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -52,6 +52,8 @@ public: private: std::unique_ptr mBackend; + using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs; + // The state of a tracked event, shared across all events derived from the original event. struct EventState { explicit inline EventState(InputTracer& tracer) : tracer(tracer){}; @@ -62,6 +64,8 @@ private: InputTracer& tracer; std::vector events; bool isEventProcessingComplete{false}; + // A queue to hold dispatch args from being traced until event processing is complete. + std::vector pendingDispatchArgs; // TODO(b/210460522): Add additional args for tracking event sensitivity and // dispatch target UIDs. }; -- GitLab From 47d7f1e33892bd7075143e7c66ce33d1c2c16357 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 8 Mar 2024 23:43:21 +0000 Subject: [PATCH 050/465] libbinder: restartWrite abort out of memory Most code in Android and indeed in libbinder will abort in this case. Since setData is underspecified, we should probably start the process of removing all the users of it (AIDL does not use it). However, until it can be removed, it's safer to abort here than risk mObjects is referenced in an invalid state from here on. Bug: 328177618 Test: N/A Change-Id: Ia36303e1f9bdc91d37943aa106bd832166b91e28 --- libs/binder/Parcel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index c1770b35d1..2a3ab7446a 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -2932,6 +2932,7 @@ status_t Parcel::restartWrite(size_t desired) uint8_t* data = reallocZeroFree(mData, mDataCapacity, desired, mDeallocZero); if (!data && desired > mDataCapacity) { + LOG_ALWAYS_FATAL("out of memory"); mError = NO_MEMORY; return NO_MEMORY; } -- GitLab From a0ae40f29a3a4d1b269ded4cda9d4f97d65d39fe Mon Sep 17 00:00:00 2001 From: Biswarup Pal Date: Wed, 15 Nov 2023 13:30:48 +0000 Subject: [PATCH 051/465] Add libtracing_perfetto for tracing using Perfetto SDK libtracing_perfetto internally uses the Perfetto SDK or libcutils based on an aconfig flag. Test: atest libtracing_perfetto_tests Bug: 303199244 Ignore-AOSP-First: Internal feature Change-Id: I9922d11607051ff22777e3df12b8f877a765dfa7 --- libs/tracing_perfetto/.clang-format | 12 + libs/tracing_perfetto/Android.bp | 49 ++ libs/tracing_perfetto/OWNERS | 2 + .../include/trace_categories.h | 65 +++ libs/tracing_perfetto/include/trace_result.h | 30 ++ .../include/tracing_perfetto.h | 53 ++ libs/tracing_perfetto/tests/Android.bp | 45 ++ .../tests/tracing_perfetto_test.cpp | 111 +++++ libs/tracing_perfetto/tests/utils.cpp | 219 +++++++++ libs/tracing_perfetto/tests/utils.h | 452 ++++++++++++++++++ libs/tracing_perfetto/tracing_perfetto.cpp | 141 ++++++ .../tracing_perfetto_internal.cpp | 229 +++++++++ .../tracing_perfetto_internal.h | 65 +++ 13 files changed, 1473 insertions(+) create mode 100644 libs/tracing_perfetto/.clang-format create mode 100644 libs/tracing_perfetto/Android.bp create mode 100644 libs/tracing_perfetto/OWNERS create mode 100644 libs/tracing_perfetto/include/trace_categories.h create mode 100644 libs/tracing_perfetto/include/trace_result.h create mode 100644 libs/tracing_perfetto/include/tracing_perfetto.h create mode 100644 libs/tracing_perfetto/tests/Android.bp create mode 100644 libs/tracing_perfetto/tests/tracing_perfetto_test.cpp create mode 100644 libs/tracing_perfetto/tests/utils.cpp create mode 100644 libs/tracing_perfetto/tests/utils.h create mode 100644 libs/tracing_perfetto/tracing_perfetto.cpp create mode 100644 libs/tracing_perfetto/tracing_perfetto_internal.cpp create mode 100644 libs/tracing_perfetto/tracing_perfetto_internal.h diff --git a/libs/tracing_perfetto/.clang-format b/libs/tracing_perfetto/.clang-format new file mode 100644 index 0000000000..f3974548f6 --- /dev/null +++ b/libs/tracing_perfetto/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: Google +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false + +ColumnLimit: 80 +ContinuationIndentWidth: 4 +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +IndentWidth: 2 +PointerAlignment: Left +UseTab: Never +PenaltyExcessCharacter: 32 \ No newline at end of file diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp new file mode 100644 index 0000000000..3a4c869e46 --- /dev/null +++ b/libs/tracing_perfetto/Android.bp @@ -0,0 +1,49 @@ +// Copyright 2024 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_library_shared { + name: "libtracing_perfetto", + export_include_dirs: [ + "include", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-enum-compare", + "-Wno-unused-function", + ], + + srcs: [ + "tracing_perfetto.cpp", + "tracing_perfetto_internal.cpp", + ], + + shared_libs: [ + "libcutils", + "libperfetto_c", + "android.os.flags-aconfig-cc-host", + ], + + host_supported: true, +} diff --git a/libs/tracing_perfetto/OWNERS b/libs/tracing_perfetto/OWNERS new file mode 100644 index 0000000000..e2d4b4636a --- /dev/null +++ b/libs/tracing_perfetto/OWNERS @@ -0,0 +1,2 @@ +zezeozue@google.com +biswarupp@google.com \ No newline at end of file diff --git a/libs/tracing_perfetto/include/trace_categories.h b/libs/tracing_perfetto/include/trace_categories.h new file mode 100644 index 0000000000..6d4168b772 --- /dev/null +++ b/libs/tracing_perfetto/include/trace_categories.h @@ -0,0 +1,65 @@ +/* + * Copyright 2024 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. + */ + +#ifndef TRACE_CATEGORIES_H +#define TRACE_CATEGORIES_H + +/** + * Keep these in sync with frameworks/base/core/java/android/os/Trace.java. + */ +#define TRACE_CATEGORY_ALWAYS (1 << 0) +#define TRACE_CATEGORY_GRAPHICS (1 << 1) +#define TRACE_CATEGORY_INPUT (1 << 2) +#define TRACE_CATEGORY_VIEW (1 << 3) +#define TRACE_CATEGORY_WEBVIEW (1 << 4) +#define TRACE_CATEGORY_WINDOW_MANAGER (1 << 5) +#define TRACE_CATEGORY_ACTIVITY_MANAGER (1 << 6) +#define TRACE_CATEGORY_SYNC_MANAGER (1 << 7) +#define TRACE_CATEGORY_AUDIO (1 << 8) +#define TRACE_CATEGORY_VIDEO (1 << 9) +#define TRACE_CATEGORY_CAMERA (1 << 10) +#define TRACE_CATEGORY_HAL (1 << 11) +#define TRACE_CATEGORY_APP (1 << 12) +#define TRACE_CATEGORY_RESOURCES (1 << 13) +#define TRACE_CATEGORY_DALVIK (1 << 14) +#define TRACE_CATEGORY_RS (1 << 15) +#define TRACE_CATEGORY_BIONIC (1 << 16) +#define TRACE_CATEGORY_POWER (1 << 17) +#define TRACE_CATEGORY_PACKAGE_MANAGER (1 << 18) +#define TRACE_CATEGORY_SYSTEM_SERVER (1 << 19) +#define TRACE_CATEGORY_DATABASE (1 << 20) +#define TRACE_CATEGORY_NETWORK (1 << 21) +#define TRACE_CATEGORY_ADB (1 << 22) +#define TRACE_CATEGORY_VIBRATOR (1 << 23) +#define TRACE_CATEGORY_AIDL (1 << 24) +#define TRACE_CATEGORY_NNAPI (1 << 25) +#define TRACE_CATEGORY_RRO (1 << 26) +#define TRACE_CATEGORY_THERMAL (1 << 27) + +// Allow all categories except TRACE_CATEGORY_APP +#define TRACE_CATEGORIES \ + TRACE_CATEGORY_ALWAYS | TRACE_CATEGORY_GRAPHICS | TRACE_CATEGORY_INPUT | \ + TRACE_CATEGORY_VIEW | TRACE_CATEGORY_WEBVIEW | \ + TRACE_CATEGORY_WINDOW_MANAGER | TRACE_CATEGORY_ACTIVITY_MANAGER | \ + TRACE_CATEGORY_SYNC_MANAGER | TRACE_CATEGORY_AUDIO | \ + TRACE_CATEGORY_VIDEO | TRACE_CATEGORY_CAMERA | TRACE_CATEGORY_HAL | \ + TRACE_CATEGORY_RESOURCES | TRACE_CATEGORY_DALVIK | TRACE_CATEGORY_RS | \ + TRACE_CATEGORY_BIONIC | TRACE_CATEGORY_POWER | \ + TRACE_CATEGORY_PACKAGE_MANAGER | TRACE_CATEGORY_SYSTEM_SERVER | \ + TRACE_CATEGORY_DATABASE | TRACE_CATEGORY_NETWORK | TRACE_CATEGORY_ADB | \ + TRACE_CATEGORY_VIBRATOR | TRACE_CATEGORY_AIDL | TRACE_CATEGORY_NNAPI | \ + TRACE_CATEGORY_RRO | TRACE_CATEGORY_THERMAL +#endif // TRACE_CATEGORIES_H diff --git a/libs/tracing_perfetto/include/trace_result.h b/libs/tracing_perfetto/include/trace_result.h new file mode 100644 index 0000000000..f7581fc0fb --- /dev/null +++ b/libs/tracing_perfetto/include/trace_result.h @@ -0,0 +1,30 @@ +/* + * Copyright 2024 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. + */ + +#ifndef TRACE_RESULT_H +#define TRACE_RESULT_H + +namespace tracing_perfetto { + +enum class Result { + SUCCESS, + NOT_SUPPORTED, + INVALID_INPUT, +}; + +} + +#endif // TRACE_RESULT_H diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h new file mode 100644 index 0000000000..4e3c83fca3 --- /dev/null +++ b/libs/tracing_perfetto/include/tracing_perfetto.h @@ -0,0 +1,53 @@ +/* + * Copyright 2024 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. + */ + +#ifndef TRACING_PERFETTO_H +#define TRACING_PERFETTO_H + +#include + +#include "trace_result.h" + +namespace tracing_perfetto { + +void registerWithPerfetto(bool test = false); + +Result traceBegin(uint64_t category, const char* name); + +Result traceEnd(uint64_t category); + +Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie); + +Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie); + +Result traceAsyncBeginForTrack(uint64_t category, const char* name, + const char* trackName, int32_t cookie); + +Result traceAsyncEndForTrack(uint64_t category, const char* trackName, + int32_t cookie); + +Result traceInstant(uint64_t category, const char* name); + +Result traceInstantForTrack(uint64_t category, const char* trackName, + const char* name); + +Result traceCounter(uint64_t category, const char* name, int64_t value); + +uint64_t getEnabledCategories(); + +} // namespace tracing_perfetto + +#endif // TRACING_PERFETTO_H diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp new file mode 100644 index 0000000000..a35b0e0c83 --- /dev/null +++ b/libs/tracing_perfetto/tests/Android.bp @@ -0,0 +1,45 @@ +// Copyright 2024 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_test { + name: "libtracing_perfetto_tests", + static_libs: [ + "libflagtest", + "libgmock", + ], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "android.os.flags-aconfig-cc-host", + "libbase", + "libperfetto_c", + "libtracing_perfetto", + ], + srcs: [ + "tracing_perfetto_test.cpp", + "utils.cpp", + ], + test_suites: ["device-tests"], +} diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp new file mode 100644 index 0000000000..7716b9a316 --- /dev/null +++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2024 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. + */ + +#include "tracing_perfetto.h" + +#include + +#include +#include + +#include "gtest/gtest.h" +#include "perfetto/public/abi/data_source_abi.h" +#include "perfetto/public/abi/heap_buffer.h" +#include "perfetto/public/abi/pb_decoder_abi.h" +#include "perfetto/public/abi/tracing_session_abi.h" +#include "perfetto/public/abi/track_event_abi.h" +#include "perfetto/public/data_source.h" +#include "perfetto/public/pb_decoder.h" +#include "perfetto/public/producer.h" +#include "perfetto/public/protos/config/trace_config.pzc.h" +#include "perfetto/public/protos/trace/interned_data/interned_data.pzc.h" +#include "perfetto/public/protos/trace/test_event.pzc.h" +#include "perfetto/public/protos/trace/trace.pzc.h" +#include "perfetto/public/protos/trace/trace_packet.pzc.h" +#include "perfetto/public/protos/trace/track_event/debug_annotation.pzc.h" +#include "perfetto/public/protos/trace/track_event/track_descriptor.pzc.h" +#include "perfetto/public/protos/trace/track_event/track_event.pzc.h" +#include "perfetto/public/protos/trace/trigger.pzc.h" +#include "perfetto/public/te_category_macros.h" +#include "perfetto/public/te_macros.h" +#include "perfetto/public/track_event.h" +#include "trace_categories.h" +#include "utils.h" + +namespace tracing_perfetto { + +using ::perfetto::shlib::test_utils::AllFieldsWithId; +using ::perfetto::shlib::test_utils::FieldView; +using ::perfetto::shlib::test_utils::IdFieldView; +using ::perfetto::shlib::test_utils::MsgField; +using ::perfetto::shlib::test_utils::PbField; +using ::perfetto::shlib::test_utils::StringField; +using ::perfetto::shlib::test_utils::TracingSession; +using ::perfetto::shlib::test_utils::VarIntField; +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::UnorderedElementsAre; + +const auto PERFETTO_SDK_TRACING = ACONFIG_FLAG(android::os, perfetto_sdk_tracing); + +class TracingPerfettoTest : public testing::Test { + protected: + void SetUp() override { + tracing_perfetto::registerWithPerfetto(true /* test */); + } +}; + +// TODO(b/303199244): Add tests for all the library functions. + +TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstant, + REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) { + TracingSession tracing_session = + TracingSession::Builder().set_data_source_name("track_event").Build(); + tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, ""); + + tracing_session.StopBlocking(); + std::vector data = tracing_session.ReadBlocking(); + bool found = false; + for (struct PerfettoPbDecoderField trace_field : FieldView(data)) { + ASSERT_THAT(trace_field, PbField(perfetto_protos_Trace_packet_field_number, + MsgField(_))); + IdFieldView track_event( + trace_field, perfetto_protos_TracePacket_track_event_field_number); + if (track_event.size() == 0) { + continue; + } + found = true; + IdFieldView cat_iid_fields( + track_event.front(), + perfetto_protos_TrackEvent_category_iids_field_number); + ASSERT_THAT(cat_iid_fields, ElementsAre(VarIntField(_))); + uint64_t cat_iid = cat_iid_fields.front().value.integer64; + EXPECT_THAT( + trace_field, + AllFieldsWithId( + perfetto_protos_TracePacket_interned_data_field_number, + ElementsAre(AllFieldsWithId( + perfetto_protos_InternedData_event_categories_field_number, + ElementsAre(MsgField(UnorderedElementsAre( + PbField(perfetto_protos_EventCategory_iid_field_number, + VarIntField(cat_iid)), + PbField(perfetto_protos_EventCategory_name_field_number, + StringField("input"))))))))); + } + EXPECT_TRUE(found); +} + +} // namespace tracing_perfetto \ No newline at end of file diff --git a/libs/tracing_perfetto/tests/utils.cpp b/libs/tracing_perfetto/tests/utils.cpp new file mode 100644 index 0000000000..9c4202808a --- /dev/null +++ b/libs/tracing_perfetto/tests/utils.cpp @@ -0,0 +1,219 @@ +/* + * Copyright 2024 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. + */ + +// Copied from //external/perfetto/src/shared_lib/test/utils.cc + +#include "utils.h" + +#include "perfetto/public/abi/heap_buffer.h" +#include "perfetto/public/pb_msg.h" +#include "perfetto/public/pb_utils.h" +#include "perfetto/public/protos/config/data_source_config.pzc.h" +#include "perfetto/public/protos/config/trace_config.pzc.h" +#include "perfetto/public/protos/config/track_event/track_event_config.pzc.h" +#include "perfetto/public/tracing_session.h" + +namespace perfetto { +namespace shlib { +namespace test_utils { +namespace { + +std::string ToHexChars(uint8_t val) { + std::string ret; + uint8_t high_nibble = (val & 0xF0) >> 4; + uint8_t low_nibble = (val & 0xF); + static const char hex_chars[] = "0123456789ABCDEF"; + ret.push_back(hex_chars[high_nibble]); + ret.push_back(hex_chars[low_nibble]); + return ret; +} + +} // namespace + +TracingSession TracingSession::Builder::Build() { + struct PerfettoPbMsgWriter writer; + struct PerfettoHeapBuffer* hb = PerfettoHeapBufferCreate(&writer.writer); + + struct perfetto_protos_TraceConfig cfg; + PerfettoPbMsgInit(&cfg.msg, &writer); + + { + struct perfetto_protos_TraceConfig_BufferConfig buffers; + perfetto_protos_TraceConfig_begin_buffers(&cfg, &buffers); + + perfetto_protos_TraceConfig_BufferConfig_set_size_kb(&buffers, 1024); + + perfetto_protos_TraceConfig_end_buffers(&cfg, &buffers); + } + + { + struct perfetto_protos_TraceConfig_DataSource data_sources; + perfetto_protos_TraceConfig_begin_data_sources(&cfg, &data_sources); + + { + struct perfetto_protos_DataSourceConfig ds_cfg; + perfetto_protos_TraceConfig_DataSource_begin_config(&data_sources, + &ds_cfg); + + perfetto_protos_DataSourceConfig_set_cstr_name(&ds_cfg, + data_source_name_.c_str()); + if (!enabled_categories_.empty() && !disabled_categories_.empty()) { + perfetto_protos_TrackEventConfig te_cfg; + perfetto_protos_DataSourceConfig_begin_track_event_config(&ds_cfg, + &te_cfg); + for (const std::string& cat : enabled_categories_) { + perfetto_protos_TrackEventConfig_set_enabled_categories( + &te_cfg, cat.data(), cat.size()); + } + for (const std::string& cat : disabled_categories_) { + perfetto_protos_TrackEventConfig_set_disabled_categories( + &te_cfg, cat.data(), cat.size()); + } + perfetto_protos_DataSourceConfig_end_track_event_config(&ds_cfg, + &te_cfg); + } + + perfetto_protos_TraceConfig_DataSource_end_config(&data_sources, &ds_cfg); + } + + perfetto_protos_TraceConfig_end_data_sources(&cfg, &data_sources); + } + size_t cfg_size = PerfettoStreamWriterGetWrittenSize(&writer.writer); + std::unique_ptr ser(new uint8_t[cfg_size]); + PerfettoHeapBufferCopyInto(hb, &writer.writer, ser.get(), cfg_size); + PerfettoHeapBufferDestroy(hb, &writer.writer); + + struct PerfettoTracingSessionImpl* ts = + PerfettoTracingSessionCreate(PERFETTO_BACKEND_IN_PROCESS); + + PerfettoTracingSessionSetup(ts, ser.get(), cfg_size); + + PerfettoTracingSessionStartBlocking(ts); + + return TracingSession::Adopt(ts); +} + +TracingSession TracingSession::Adopt(struct PerfettoTracingSessionImpl* session) { + TracingSession ret; + ret.session_ = session; + ret.stopped_ = std::make_unique(); + PerfettoTracingSessionSetStopCb( + ret.session_, + [](struct PerfettoTracingSessionImpl*, void* arg) { + static_cast(arg)->Notify(); + }, + ret.stopped_.get()); + return ret; +} + +TracingSession::TracingSession(TracingSession&& other) noexcept { + session_ = other.session_; + other.session_ = nullptr; + stopped_ = std::move(other.stopped_); + other.stopped_ = nullptr; +} + +TracingSession::~TracingSession() { + if (!session_) { + return; + } + if (!stopped_->IsNotified()) { + PerfettoTracingSessionStopBlocking(session_); + stopped_->WaitForNotification(); + } + PerfettoTracingSessionDestroy(session_); +} + +bool TracingSession::FlushBlocking(uint32_t timeout_ms) { + WaitableEvent notification; + bool result; + auto* cb = new std::function([&](bool success) { + result = success; + notification.Notify(); + }); + PerfettoTracingSessionFlushAsync( + session_, timeout_ms, + [](PerfettoTracingSessionImpl*, bool success, void* user_arg) { + auto* f = reinterpret_cast*>(user_arg); + (*f)(success); + delete f; + }, + cb); + notification.WaitForNotification(); + return result; +} + +void TracingSession::WaitForStopped() { + stopped_->WaitForNotification(); +} + +void TracingSession::StopBlocking() { + PerfettoTracingSessionStopBlocking(session_); +} + +std::vector TracingSession::ReadBlocking() { + std::vector data; + PerfettoTracingSessionReadTraceBlocking( + session_, + [](struct PerfettoTracingSessionImpl*, const void* trace_data, + size_t size, bool, void* user_arg) { + auto& dst = *static_cast*>(user_arg); + auto* src = static_cast(trace_data); + dst.insert(dst.end(), src, src + size); + }, + &data); + return data; +} + +} // namespace test_utils +} // namespace shlib +} // namespace perfetto + +void PrintTo(const PerfettoPbDecoderField& field, std::ostream* pos) { + std::ostream& os = *pos; + PerfettoPbDecoderStatus status = + static_cast(field.status); + switch (status) { + case PERFETTO_PB_DECODER_ERROR: + os << "MALFORMED PROTOBUF"; + break; + case PERFETTO_PB_DECODER_DONE: + os << "DECODER DONE"; + break; + case PERFETTO_PB_DECODER_OK: + switch (field.wire_type) { + case PERFETTO_PB_WIRE_TYPE_DELIMITED: + os << "\""; + for (size_t i = 0; i < field.value.delimited.len; i++) { + os << perfetto::shlib::test_utils::ToHexChars( + field.value.delimited.start[i]) + << " "; + } + os << "\""; + break; + case PERFETTO_PB_WIRE_TYPE_VARINT: + os << "varint: " << field.value.integer64; + break; + case PERFETTO_PB_WIRE_TYPE_FIXED32: + os << "fixed32: " << field.value.integer32; + break; + case PERFETTO_PB_WIRE_TYPE_FIXED64: + os << "fixed64: " << field.value.integer64; + break; + } + break; + } +} diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h new file mode 100644 index 0000000000..4353554963 --- /dev/null +++ b/libs/tracing_perfetto/tests/utils.h @@ -0,0 +1,452 @@ +/* + * Copyright 2024 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. + */ + +// Copied from //external/perfetto/src/shared_lib/test/utils.h + +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock-matchers.h" +#include "gmock/gmock-more-matchers.h" +#include "gtest/gtest-matchers.h" +#include "gtest/gtest.h" +#include "perfetto/public/abi/pb_decoder_abi.h" +#include "perfetto/public/pb_utils.h" +#include "perfetto/public/tracing_session.h" + +// Pretty printer for gtest +void PrintTo(const PerfettoPbDecoderField& field, std::ostream*); + +namespace perfetto { +namespace shlib { +namespace test_utils { + +class WaitableEvent { + public: + WaitableEvent() = default; + void Notify() { + std::unique_lock lock(m_); + notified_ = true; + cv_.notify_one(); + } + bool WaitForNotification() { + std::unique_lock lock(m_); + cv_.wait(lock, [this] { return notified_; }); + return notified_; + } + bool IsNotified() { + std::unique_lock lock(m_); + return notified_; + } + + private: + std::mutex m_; + std::condition_variable cv_; + bool notified_ = false; +}; + +class TracingSession { + public: + class Builder { + public: + Builder() = default; + Builder& set_data_source_name(std::string data_source_name) { + data_source_name_ = std::move(data_source_name); + return *this; + } + Builder& add_enabled_category(std::string category) { + enabled_categories_.push_back(std::move(category)); + return *this; + } + Builder& add_disabled_category(std::string category) { + disabled_categories_.push_back(std::move(category)); + return *this; + } + TracingSession Build(); + + private: + std::string data_source_name_; + std::vector enabled_categories_; + std::vector disabled_categories_; + }; + + static TracingSession Adopt(struct PerfettoTracingSessionImpl*); + + TracingSession(TracingSession&&) noexcept; + + ~TracingSession(); + + struct PerfettoTracingSessionImpl* session() const { + return session_; + } + + bool FlushBlocking(uint32_t timeout_ms); + void WaitForStopped(); + void StopBlocking(); + std::vector ReadBlocking(); + + private: + TracingSession() = default; + struct PerfettoTracingSessionImpl* session_; + std::unique_ptr stopped_; +}; + +template +class FieldViewBase { + public: + class Iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = const PerfettoPbDecoderField; + using pointer = value_type; + using reference = value_type; + reference operator*() const { + struct PerfettoPbDecoder decoder; + decoder.read_ptr = read_ptr_; + decoder.end_ptr = end_ptr_; + struct PerfettoPbDecoderField field; + do { + field = PerfettoPbDecoderParseField(&decoder); + } while (field.status == PERFETTO_PB_DECODER_OK && + skipper_.ShouldSkip(field)); + return field; + } + Iterator& operator++() { + struct PerfettoPbDecoder decoder; + decoder.read_ptr = read_ptr_; + decoder.end_ptr = end_ptr_; + PerfettoPbDecoderSkipField(&decoder); + read_ptr_ = decoder.read_ptr; + AdvanceToFirstInterestingField(); + return *this; + } + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const Iterator& a, const Iterator& b) { + return a.read_ptr_ == b.read_ptr_; + } + friend bool operator!=(const Iterator& a, const Iterator& b) { + return a.read_ptr_ != b.read_ptr_; + } + + private: + Iterator(const uint8_t* read_ptr, const uint8_t* end_ptr, + const FieldSkipper& skipper) + : read_ptr_(read_ptr), end_ptr_(end_ptr), skipper_(skipper) { + AdvanceToFirstInterestingField(); + } + void AdvanceToFirstInterestingField() { + struct PerfettoPbDecoder decoder; + decoder.read_ptr = read_ptr_; + decoder.end_ptr = end_ptr_; + struct PerfettoPbDecoderField field; + const uint8_t* prev_read_ptr; + do { + prev_read_ptr = decoder.read_ptr; + field = PerfettoPbDecoderParseField(&decoder); + } while (field.status == PERFETTO_PB_DECODER_OK && + skipper_.ShouldSkip(field)); + if (field.status == PERFETTO_PB_DECODER_OK) { + read_ptr_ = prev_read_ptr; + } else { + read_ptr_ = decoder.read_ptr; + } + } + friend class FieldViewBase; + const uint8_t* read_ptr_; + const uint8_t* end_ptr_; + const FieldSkipper& skipper_; + }; + using value_type = const PerfettoPbDecoderField; + using const_iterator = Iterator; + template + explicit FieldViewBase(const uint8_t* begin, const uint8_t* end, Args... args) + : begin_(begin), end_(end), s_(args...) { + } + template + explicit FieldViewBase(const std::vector& data, Args... args) + : FieldViewBase(data.data(), data.data() + data.size(), args...) { + } + template + explicit FieldViewBase(const struct PerfettoPbDecoderField& field, + Args... args) + : s_(args...) { + if (field.wire_type != PERFETTO_PB_WIRE_TYPE_DELIMITED) { + abort(); + } + begin_ = field.value.delimited.start; + end_ = begin_ + field.value.delimited.len; + } + Iterator begin() const { + return Iterator(begin_, end_, s_); + } + Iterator end() const { + return Iterator(end_, end_, s_); + } + PerfettoPbDecoderField front() const { + return *begin(); + } + + size_t size() const { + size_t count = 0; + for (auto field : *this) { + (void)field; + count++; + } + return count; + } + + bool ok() const { + for (auto field : *this) { + if (field.status != PERFETTO_PB_DECODER_OK) { + return false; + } + } + return true; + } + + private: + const uint8_t* begin_; + const uint8_t* end_; + FieldSkipper s_; +}; + +// Pretty printer for gtest +template +void PrintTo(const FieldViewBase& field_view, std::ostream* pos) { + std::ostream& os = *pos; + os << "{"; + for (PerfettoPbDecoderField f : field_view) { + PrintTo(f, pos); + os << ", "; + } + os << "}"; +} + +class IdFieldSkipper { + public: + explicit IdFieldSkipper(uint32_t id) : id_(id) { + } + explicit IdFieldSkipper(int32_t id) : id_(static_cast(id)) { + } + bool ShouldSkip(const struct PerfettoPbDecoderField& field) const { + return field.id != id_; + } + + private: + uint32_t id_; +}; + +class NoFieldSkipper { + public: + NoFieldSkipper() = default; + bool ShouldSkip(const struct PerfettoPbDecoderField&) const { + return false; + } +}; + +// View over all the fields of a contiguous serialized protobuf message. +// +// Examples: +// +// for (struct PerfettoPbDecoderField field : FieldView(msg_begin, msg_end)) { +// //... +// } +// FieldView fields2(/*PerfettoPbDecoderField*/ nested_field); +// FieldView fields3(/*std::vector*/ data); +// size_t num = fields1.size(); // The number of fields. +// bool ok = fields1.ok(); // Checks that the message is not malformed. +using FieldView = FieldViewBase; + +// Like `FieldView`, but only considers fields with a specific id. +// +// Examples: +// +// IdFieldView fields(msg_begin, msg_end, id) +using IdFieldView = FieldViewBase; + +// Matches a PerfettoPbDecoderField with the specified id. Accepts another +// matcher to match the contents of the field. +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, PbField(900, VarIntField(5))); +template +auto PbField(int32_t id, M m) { + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::id, id), m); +} + +// Matches a PerfettoPbDecoderField submessage field. Accepts a container +// matcher for the subfields. +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, MsgField(ElementsAre(...))); +template +auto MsgField(M m) { + auto f = [](const PerfettoPbDecoderField& field) { return FieldView(field); }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_DELIMITED), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField length delimited field. Accepts a string +// matcher. +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, StringField("string")); +template +auto StringField(M m) { + auto f = [](const PerfettoPbDecoderField& field) { + return std::string( + reinterpret_cast(field.value.delimited.start), + field.value.delimited.len); + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_DELIMITED), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField VarInt field. Accepts an integer matcher +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, VarIntField(1))); +template +auto VarIntField(M m) { + auto f = [](const PerfettoPbDecoderField& field) { + return field.value.integer64; + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_VARINT), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField fixed64 field. Accepts an integer matcher +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, Fixed64Field(1))); +template +auto Fixed64Field(M m) { + auto f = [](const PerfettoPbDecoderField& field) { + return field.value.integer64; + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_FIXED64), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField fixed32 field. Accepts an integer matcher +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, Fixed32Field(1))); +template +auto Fixed32Field(M m) { + auto f = [](const PerfettoPbDecoderField& field) { + return field.value.integer32; + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_FIXED32), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField double field. Accepts a double matcher +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, DoubleField(1.0))); +template +auto DoubleField(M m) { + auto f = [](const PerfettoPbDecoderField& field) { + return field.value.double_val; + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_FIXED64), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField float field. Accepts a float matcher +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, FloatField(1.0))); +template +auto FloatField(M m) { + auto f = [](const PerfettoPbDecoderField& field) { + return field.value.float_val; + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_FIXED32), + testing::ResultOf(f, m)); +} + +// Matches a PerfettoPbDecoderField submessage field. Accepts a container +// matcher for the subfields. +// +// Example: +// PerfettoPbDecoderField field = ... +// EXPECT_THAT(field, AllFieldsWithId(900, ElementsAre(...))); +template +auto AllFieldsWithId(int32_t id, M m) { + auto f = [id](const PerfettoPbDecoderField& field) { + return IdFieldView(field, id); + }; + return testing::AllOf( + testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK), + testing::Field(&PerfettoPbDecoderField::wire_type, + PERFETTO_PB_WIRE_TYPE_DELIMITED), + testing::ResultOf(f, m)); +} + +} // namespace test_utils +} // namespace shlib +} // namespace perfetto + +#endif // UTILS_H diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp new file mode 100644 index 0000000000..c7fb8bd9a8 --- /dev/null +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -0,0 +1,141 @@ +/* + * Copyright 2024 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. + */ + +#include "tracing_perfetto.h" + +#include + +#include "perfetto/public/te_category_macros.h" +#include "trace_categories.h" +#include "tracing_perfetto_internal.h" + +namespace tracing_perfetto { + +void registerWithPerfetto(bool test) { + internal::registerWithPerfetto(test); +} + +Result traceBegin(uint64_t category, const char* name) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceBegin(*perfettoTeCategory, name); + } else { + atrace_begin(category, name); + return Result::SUCCESS; + } +} + +Result traceEnd(uint64_t category) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceEnd(*perfettoTeCategory); + } else { + atrace_end(category); + return Result::SUCCESS; + } +} + +Result traceAsyncBegin(uint64_t category, const char* name, int32_t cookie) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceAsyncBegin(*perfettoTeCategory, name, cookie); + } else { + atrace_async_begin(category, name, cookie); + return Result::SUCCESS; + } +} + +Result traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceAsyncEnd(*perfettoTeCategory, name, cookie); + } else { + atrace_async_end(category, name, cookie); + return Result::SUCCESS; + } +} + +Result traceAsyncBeginForTrack(uint64_t category, const char* name, + const char* trackName, int32_t cookie) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie); + } else { + atrace_async_for_track_begin(category, trackName, name, cookie); + return Result::SUCCESS; + } +} + +Result traceAsyncEndForTrack(uint64_t category, const char* trackName, + int32_t cookie) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie); + } else { + atrace_async_for_track_end(category, trackName, cookie); + return Result::SUCCESS; + } +} + +Result traceInstant(uint64_t category, const char* name) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceInstant(*perfettoTeCategory, name); + } else { + atrace_instant(category, name); + return Result::SUCCESS; + } +} + +Result traceInstantForTrack(uint64_t category, const char* trackName, + const char* name) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceInstantForTrack(*perfettoTeCategory, trackName, name); + } else { + atrace_instant_for_track(category, trackName, name); + return Result::SUCCESS; + } +} + +Result traceCounter(uint64_t category, const char* name, int64_t value) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return internal::perfettoTraceCounter(*perfettoTeCategory, name, value); + } else { + atrace_int64(category, name, value); + return Result::SUCCESS; + } +} + +uint64_t getEnabledCategories() { + if (internal::isPerfettoSdkTracingEnabled()) { + return internal::getDefaultCategories(); + } else { + return atrace_get_enabled_tags(); + } +} + +} // namespace tracing_perfetto diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp new file mode 100644 index 0000000000..58ba428610 --- /dev/null +++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp @@ -0,0 +1,229 @@ +/* + * Copyright 2024 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. + */ + +#define FRAMEWORK_CATEGORIES(C) \ + C(always, "always", "Always category") \ + C(graphics, "graphics", "Graphics category") \ + C(input, "input", "Input category") \ + C(view, "view", "View category") \ + C(webview, "webview", "WebView category") \ + C(windowmanager, "wm", "WindowManager category") \ + C(activitymanager, "am", "ActivityManager category") \ + C(syncmanager, "syncmanager", "SyncManager category") \ + C(audio, "audio", "Audio category") \ + C(video, "video", "Video category") \ + C(camera, "camera", "Camera category") \ + C(hal, "hal", "HAL category") \ + C(app, "app", "App category") \ + C(resources, "res", "Resources category") \ + C(dalvik, "dalvik", "Dalvik category") \ + C(rs, "rs", "RS category") \ + C(bionic, "bionic", "Bionic category") \ + C(power, "power", "Power category") \ + C(packagemanager, "packagemanager", "PackageManager category") \ + C(systemserver, "ss", "System Server category") \ + C(database, "database", "Database category") \ + C(network, "network", "Network category") \ + C(adb, "adb", "ADB category") \ + C(vibrator, "vibrator", "Vibrator category") \ + C(aidl, "aidl", "AIDL category") \ + C(nnapi, "nnapi", "NNAPI category") \ + C(rro, "rro", "RRO category") \ + C(thermal, "thermal", "Thermal category") + +#include "tracing_perfetto_internal.h" + +#include + +#include + +#include + +#include "perfetto/public/compiler.h" +#include "perfetto/public/producer.h" +#include "perfetto/public/te_category_macros.h" +#include "perfetto/public/te_macros.h" +#include "perfetto/public/track_event.h" +#include "trace_categories.h" +#include "trace_result.h" + +namespace tracing_perfetto { + +namespace internal { + +namespace { + +PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES); + +PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES); + +struct PerfettoTeCategory* toCategory(uint64_t inCategory) { + switch (inCategory) { + case TRACE_CATEGORY_ALWAYS: + return &always; + case TRACE_CATEGORY_GRAPHICS: + return &graphics; + case TRACE_CATEGORY_INPUT: + return &input; + case TRACE_CATEGORY_VIEW: + return &view; + case TRACE_CATEGORY_WEBVIEW: + return &webview; + case TRACE_CATEGORY_WINDOW_MANAGER: + return &windowmanager; + case TRACE_CATEGORY_ACTIVITY_MANAGER: + return &activitymanager; + case TRACE_CATEGORY_SYNC_MANAGER: + return &syncmanager; + case TRACE_CATEGORY_AUDIO: + return &audio; + case TRACE_CATEGORY_VIDEO: + return &video; + case TRACE_CATEGORY_CAMERA: + return &camera; + case TRACE_CATEGORY_HAL: + return &hal; + case TRACE_CATEGORY_APP: + return &app; + case TRACE_CATEGORY_RESOURCES: + return &resources; + case TRACE_CATEGORY_DALVIK: + return &dalvik; + case TRACE_CATEGORY_RS: + return &rs; + case TRACE_CATEGORY_BIONIC: + return &bionic; + case TRACE_CATEGORY_POWER: + return &power; + case TRACE_CATEGORY_PACKAGE_MANAGER: + return &packagemanager; + case TRACE_CATEGORY_SYSTEM_SERVER: + return &systemserver; + case TRACE_CATEGORY_DATABASE: + return &database; + case TRACE_CATEGORY_NETWORK: + return &network; + case TRACE_CATEGORY_ADB: + return &adb; + case TRACE_CATEGORY_VIBRATOR: + return &vibrator; + case TRACE_CATEGORY_AIDL: + return &aidl; + case TRACE_CATEGORY_NNAPI: + return &nnapi; + case TRACE_CATEGORY_RRO: + return &rro; + case TRACE_CATEGORY_THERMAL: + return &thermal; + default: + return nullptr; + } +} + +} // namespace + +bool isPerfettoSdkTracingEnabled() { + return android::os::perfetto_sdk_tracing(); +} + +struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { + if (!isPerfettoSdkTracingEnabled()) { + return nullptr; + } + + struct PerfettoTeCategory* perfettoCategory = toCategory(category); + bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( + (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED)); + return enabled ? perfettoCategory : nullptr; +} + +void registerWithPerfetto(bool test) { + if (!isPerfettoSdkTracingEnabled()) { + return; + } + static std::once_flag registration; + std::call_once(registration, [test]() { + struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); + args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM; + PerfettoProducerInit(args); + PerfettoTeInit(); + PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES); + }); +} + +Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) { + PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN(name)); + return Result::SUCCESS; +} + +Result perfettoTraceEnd(const struct PerfettoTeCategory& category) { + PERFETTO_TE(category, PERFETTO_TE_SLICE_END()); + return Result::SUCCESS; +} + +Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name, + const char* trackName, uint64_t cookie) { + PERFETTO_TE( + category, PERFETTO_TE_SLICE_BEGIN(name), + PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid())); + return Result::SUCCESS; +} + +Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category, + const char* trackName, uint64_t cookie) { + PERFETTO_TE( + category, PERFETTO_TE_SLICE_END(), + PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid())); + return Result::SUCCESS; +} + +Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name, + uint64_t cookie) { + return perfettoTraceAsyncBeginForTrack(category, name, name, cookie); +} + +Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name, + uint64_t cookie) { + return perfettoTraceAsyncEndForTrack(category, name, cookie); +} + +Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) { + PERFETTO_TE(category, PERFETTO_TE_INSTANT(name)); + return Result::SUCCESS; +} + +Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category, + const char* trackName, const char* name) { + PERFETTO_TE( + category, PERFETTO_TE_INSTANT(name), + PERFETTO_TE_NAMED_TRACK(trackName, 1, PerfettoTeProcessTrackUuid())); + return Result::SUCCESS; +} + +Result perfettoTraceCounter(const struct PerfettoTeCategory& category, + [[maybe_unused]] const char* name, int64_t value) { + PERFETTO_TE(category, PERFETTO_TE_COUNTER(), + PERFETTO_TE_INT_COUNTER(value)); + return Result::SUCCESS; +} + +uint64_t getDefaultCategories() { + return TRACE_CATEGORIES; +} + +} // namespace internal + +} // namespace tracing_perfetto diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h new file mode 100644 index 0000000000..9a579f162a --- /dev/null +++ b/libs/tracing_perfetto/tracing_perfetto_internal.h @@ -0,0 +1,65 @@ +/* + * Copyright 2024 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. + */ + +#ifndef TRACING_PERFETTO_INTERNAL_H +#define TRACING_PERFETTO_INTERNAL_H + +#include + +#include "include/trace_result.h" +#include "perfetto/public/te_category_macros.h" + +namespace tracing_perfetto { + +namespace internal { + +bool isPerfettoSdkTracingEnabled(); + +struct PerfettoTeCategory* toPerfettoCategory(uint64_t category); + +void registerWithPerfetto(bool test = false); + +Result perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name); + +Result perfettoTraceEnd(const struct PerfettoTeCategory& category); + +Result perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name, + uint64_t cookie); + +Result perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name, + uint64_t cookie); + +Result perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name, + const char* trackName, uint64_t cookie); + +Result perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category, + const char* trackName, uint64_t cookie); + +Result perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name); + +Result perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category, + const char* trackName, const char* name); + +Result perfettoTraceCounter(const struct PerfettoTeCategory& category, const char* name, + int64_t value); + +uint64_t getDefaultCategories(); + +} // namespace internal + +} // namespace tracing_perfetto + +#endif // TRACING_PERFETTO_INTERNAL_H -- GitLab From d4a227540e90e6d557a46990ac877aae5bd0bac6 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 8 Mar 2024 17:39:04 -0500 Subject: [PATCH 052/465] SF: Extract utils::OnceFuture Make `mRenderEnginePrimeCacheFuture` thread-safe and release its shared state after the wait. Bug: 328459745 Test: presubmit Change-Id: I2bf3029823109a8c97e599d20c15e46ac1e4aeb9 --- services/surfaceflinger/SurfaceFlinger.cpp | 33 ++++++-------- services/surfaceflinger/SurfaceFlinger.h | 6 +-- services/surfaceflinger/Utils/OnceFuture.h | 53 ++++++++++++++++++++++ 3 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 services/surfaceflinger/Utils/OnceFuture.h diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 30b8953850..78e14aee86 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -565,14 +565,11 @@ void SurfaceFlinger::binderDied(const wp&) { initializeDisplays(); })); - std::lock_guard lock(mInitBootPropsFutureMutex); - if (!mInitBootPropsFuture.valid()) { - mInitBootPropsFuture = - std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this); - } + mInitBootPropsFuture.callOnce([this] { + return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this); + }); mInitBootPropsFuture.wait(); - mInitBootPropsFuture = {}; } void SurfaceFlinger::run() { @@ -729,13 +726,8 @@ void SurfaceFlinger::bootFinished() { mBootFinished = true; FlagManager::getMutableInstance().markBootCompleted(); - if (std::lock_guard lock(mInitBootPropsFutureMutex); mInitBootPropsFuture.valid()) { - mInitBootPropsFuture.wait(); - mInitBootPropsFuture = {}; - } - if (mRenderEnginePrimeCacheFuture.valid()) { - mRenderEnginePrimeCacheFuture.wait(); - } + mInitBootPropsFuture.wait(); + mRenderEnginePrimeCacheFuture.wait(); const nsecs_t now = systemTime(); const nsecs_t duration = now - mBootTime; @@ -925,9 +917,11 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { ALOGW("Can't set SCHED_OTHER for primeCache"); } - bool shouldPrimeUltraHDR = - base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false); - mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR); + mRenderEnginePrimeCacheFuture.callOnce([this] { + const bool shouldPrimeUltraHDR = + base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false); + return getRenderEngine().primeCache(shouldPrimeUltraHDR); + }); if (setSchedFifo(true) != NO_ERROR) { ALOGW("Can't set SCHED_FIFO after primeCache"); @@ -935,10 +929,9 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { } // Avoid blocking the main thread on `init` to set properties. - if (std::lock_guard lock(mInitBootPropsFutureMutex); !mInitBootPropsFuture.valid()) { - mInitBootPropsFuture = - std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this); - } + mInitBootPropsFuture.callOnce([this] { + return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this); + }); initTransactionTraceWriter(); ALOGV("Done initializing"); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index b9ea0c3c67..0cc8fbb98a 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -89,6 +89,7 @@ #include "Tracing/TransactionTracing.h" #include "TransactionCallbackInvoker.h" #include "TransactionState.h" +#include "Utils/OnceFuture.h" #include #include @@ -1189,10 +1190,9 @@ private: pid_t mPid; // TODO: b/328459745 - Encapsulate in a SystemProperties object. - std::mutex mInitBootPropsFutureMutex; - std::future mInitBootPropsFuture GUARDED_BY(mInitBootPropsFutureMutex); + utils::OnceFuture mInitBootPropsFuture; - std::future mRenderEnginePrimeCacheFuture; + utils::OnceFuture mRenderEnginePrimeCacheFuture; // mStateLock has conventions related to the current thread, because only // the main thread should modify variables protected by mStateLock. diff --git a/services/surfaceflinger/Utils/OnceFuture.h b/services/surfaceflinger/Utils/OnceFuture.h new file mode 100644 index 0000000000..412038ce10 --- /dev/null +++ b/services/surfaceflinger/Utils/OnceFuture.h @@ -0,0 +1,53 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace android::utils { + +// Allows a thread to `wait` for a future produced by a different thread. The future is returned by +// the first call to a function `F` that multiple threads may `callOnce`. If no `callOnce` happens, +// then `wait` does nothing. Otherwise, it blocks on the future, then destroys it, which resets the +// `OnceFuture`. +class OnceFuture { +public: + template + void callOnce(F f) { + std::lock_guard lock(mMutex); + if (!mFuture.valid()) { + mFuture = f(); + } + } + + void wait() { + std::lock_guard lock(mMutex); + if (mFuture.valid()) { + mFuture.wait(); + mFuture = {}; + } + } + +private: + std::mutex mMutex; + std::future mFuture GUARDED_BY(mMutex); +}; + +} // namespace android::utils -- GitLab From 3782af62ea590e8945f2175a90aa2b6f4995814d Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Thu, 7 Mar 2024 21:56:39 -0800 Subject: [PATCH 053/465] Move BlockingQueue.h to libinput This is already useful in several places, and it's going to be also useful in the future when InputConsumer is refactored. So let's move it to include/input. Bug: 311142655 Test: m inputflinger_blocking_queue_fuzzer Change-Id: Idfc492c6bfc3cccab7e0b0d12b21a41a954cc44b --- {services/inputflinger => include/input}/BlockingQueue.h | 0 libs/input/tests/Android.bp | 1 + .../input}/tests/BlockingQueue_test.cpp | 7 +++---- services/inputflinger/InputProcessor.h | 2 +- services/inputflinger/tests/Android.bp | 1 - services/inputflinger/tests/InputDispatcher_test.cpp | 2 +- .../inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) rename {services/inputflinger => include/input}/BlockingQueue.h (100%) rename {services/inputflinger => libs/input}/tests/BlockingQueue_test.cpp (98%) diff --git a/services/inputflinger/BlockingQueue.h b/include/input/BlockingQueue.h similarity index 100% rename from services/inputflinger/BlockingQueue.h rename to include/input/BlockingQueue.h diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 0485ff6e1e..93af4c2066 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -13,6 +13,7 @@ cc_test { cpp_std: "c++20", host_supported: true, srcs: [ + "BlockingQueue_test.cpp", "IdGenerator_test.cpp", "InputChannel_test.cpp", "InputDevice_test.cpp", diff --git a/services/inputflinger/tests/BlockingQueue_test.cpp b/libs/input/tests/BlockingQueue_test.cpp similarity index 98% rename from services/inputflinger/tests/BlockingQueue_test.cpp rename to libs/input/tests/BlockingQueue_test.cpp index 754a5c451e..924b937080 100644 --- a/services/inputflinger/tests/BlockingQueue_test.cpp +++ b/libs/input/tests/BlockingQueue_test.cpp @@ -14,8 +14,7 @@ * limitations under the License. */ -#include "../BlockingQueue.h" - +#include #include #include @@ -109,7 +108,7 @@ TEST(BlockingQueueTest, Queue_AllowsMultipleThreads) { BlockingQueue queue(capacity); // Fill queue from a different thread - std::thread fillQueue([&queue](){ + std::thread fillQueue([&queue]() { for (size_t i = 0; i < capacity; i++) { ASSERT_TRUE(queue.push(static_cast(i))); } @@ -136,7 +135,7 @@ TEST(BlockingQueueTest, Queue_BlocksWhileWaitingForElements) { std::atomic_bool hasReceivedElement = false; // fill queue from a different thread - std::thread waitUntilHasElements([&queue, &hasReceivedElement](){ + std::thread waitUntilHasElements([&queue, &hasReceivedElement]() { queue.pop(); // This should block until an element has been added hasReceivedElement = true; }); diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h index dcbfebc62f..7a00a2dae8 100644 --- a/services/inputflinger/InputProcessor.h +++ b/services/inputflinger/InputProcessor.h @@ -22,7 +22,7 @@ #include #include -#include "BlockingQueue.h" +#include #include "InputListener.h" namespace android { diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index a26153ec5b..09ae6dd28c 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -39,7 +39,6 @@ cc_test { ], srcs: [ "AnrTracker_test.cpp", - "BlockingQueue_test.cpp", "CapturedTouchpadEventConverter_test.cpp", "CursorInputMapper_test.cpp", "EventHub_test.cpp", diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index f0f4d93ecd..168e74ca83 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -15,7 +15,6 @@ */ #include "../dispatcher/InputDispatcher.h" -#include "../BlockingQueue.h" #include "FakeApplicationHandle.h" #include "FakeInputTracingBackend.h" #include "TestEventMatchers.h" @@ -32,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp index 219b662ffb..863d0a165e 100644 --- a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp @@ -15,8 +15,8 @@ */ #include +#include #include -#include "BlockingQueue.h" // Chosen to be a number large enough for variation in fuzzer runs, but not consume too much memory. static constexpr size_t MAX_CAPACITY = 1024; -- GitLab From 1a41fe079e3347a017134fc74d0fd145420e1847 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 11 Mar 2024 18:38:40 +0000 Subject: [PATCH 054/465] Increase leniency of precondition check for MotionEvent::split The severity of a specific precondition check was unintentionally promoted from WARNING to FATAL in the following refactor: I6230b6aa0696dcfc275a5a14ab4af3d4b7bd0b45 Here, we demote it back to a non-fatal check. Bug: 328852741 Bug: 329107108 Test: atest inputflinger_tests Change-Id: If2cb22d528d5e68c1e035a3e4291dae7fc32bb34 --- libs/input/Input.cpp | 3 ++- .../inputflinger/dispatcher/InputDispatcher.cpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index ff9d9a94c9..61a964ece9 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -1034,7 +1034,8 @@ std::tuple, std::vector> (splitPointerProperties.size() * (historySize + 1))); if (CC_UNLIKELY(splitPointerProperties.size() != splitCount)) { - LOG(FATAL) << "Cannot split MotionEvent: Requested splitting " << splitCount + // TODO(b/329107108): Promote this to a fatal check once bugs in the caller are resolved. + LOG(ERROR) << "Cannot split MotionEvent: Requested splitting " << splitCount << " pointers from the original event, but the original event only contained " << splitPointerProperties.size() << " of those pointers."; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 73bbed6c68..3c83affcbe 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4285,6 +4285,20 @@ std::unique_ptr InputDispatcher::splitMotionEvent( MotionEvent::split(originalMotionEntry.action, originalMotionEntry.flags, /*historySize=*/0, originalMotionEntry.pointerProperties, originalMotionEntry.pointerCoords, pointerIds); + if (pointerIds.count() != pointerCoords.size()) { + // TODO(b/329107108): Determine why some IDs in pointerIds were not in originalMotionEntry. + // This is bad. We are missing some of the pointers that we expected to deliver. + // Most likely this indicates that we received an ACTION_MOVE events that has + // different pointer ids than we expected based on the previous ACTION_DOWN + // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers + // in this way. + ALOGW("Dropping split motion event because the pointer count is %d but " + "we expected there to be %zu pointers. This probably means we received " + "a broken sequence of pointer ids from the input device: %s", + pointerCoords.size(), pointerIds.count(), + originalMotionEntry.getDescription().c_str()); + return nullptr; + } // TODO(b/327503168): Move this check inside MotionEvent::split once all callers handle it // correctly. -- GitLab From 52dfaadf8b0bf437b068f6a51c2e918566952281 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Thu, 7 Mar 2024 20:20:07 +0000 Subject: [PATCH 055/465] Update SF to use createHintSessionWithConfig safely This patch updates SurfaceFlinger to use the createHintSessionWithConfig method to create its session, allowing for SF session tagging and additional metadata to be passed back to SF such as session ID. This change is gated by the android::os::adpf_use_fmq_channel flag. Bug: 318517387 Test: atest libpowermanager_test libsurfaceflinger_unittest libcompositionengine_test Change-Id: Ib22db0c1c51fbcfdb088dbc9b3e501f09cbf1008 --- .../DisplayHardware/PowerAdvisor.cpp | 34 ++++++++++++++++--- .../DisplayHardware/PowerAdvisor.h | 10 ++++++ .../common/include/common/test/FlagUtils.h | 5 ++- .../tests/unittests/PowerAdvisorTest.cpp | 32 ++++++++++++----- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index 8dfbeb80cd..faa51972e9 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -48,6 +48,7 @@ namespace impl { using aidl::android::hardware::power::Boost; using aidl::android::hardware::power::Mode; using aidl::android::hardware::power::SessionHint; +using aidl::android::hardware::power::SessionTag; using aidl::android::hardware::power::WorkDuration; PowerAdvisor::~PowerAdvisor() = default; @@ -204,13 +205,36 @@ bool PowerAdvisor::supportsPowerHintSession() { return *mSupportsHintSession; } +bool PowerAdvisor::shouldCreateSessionWithConfig() { + return mSessionConfigSupported && FlagManager::getInstance().adpf_use_fmq_channel(); +} + bool PowerAdvisor::ensurePowerHintSessionRunning() { if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) { - auto ret = getPowerHal().createHintSession(getpid(), static_cast(getuid()), - mHintSessionThreadIds, mTargetDuration.ns()); - - if (ret.isOk()) { - mHintSession = ret.value(); + if (shouldCreateSessionWithConfig()) { + auto ret = getPowerHal().createHintSessionWithConfig(getpid(), + static_cast(getuid()), + mHintSessionThreadIds, + mTargetDuration.ns(), + SessionTag::SURFACEFLINGER, + &mSessionConfig); + if (ret.isOk()) { + mHintSession = ret.value(); + } + // If it fails the first time we try, or ever returns unsupported, assume unsupported + else if (mFirstConfigSupportCheck || ret.isUnsupported()) { + ALOGI("Hint session with config is unsupported, falling back to a legacy session"); + mSessionConfigSupported = false; + } + mFirstConfigSupportCheck = false; + } + // Immediately try original method after, in case the first way returned unsupported + if (mHintSession == nullptr && !shouldCreateSessionWithConfig()) { + auto ret = getPowerHal().createHintSession(getpid(), static_cast(getuid()), + mHintSessionThreadIds, mTargetDuration.ns()); + if (ret.isOk()) { + mHintSession = ret.value(); + } } } return mHintSession != nullptr; diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 1040048b13..13e1263369 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -230,6 +230,9 @@ private: // this normalizes them together and takes the max of the two Duration combineTimingEstimates(Duration totalDuration, Duration flingerDuration); + // Whether to use the new "createHintSessionWithConfig" method + bool shouldCreateSessionWithConfig() REQUIRES(mHintSessionMutex); + bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex); std::unordered_map mDisplayTimingData; @@ -278,6 +281,13 @@ private: std::promise mDelayReportActualMutexAcquisitonPromise; bool mTimingTestingMode = false; + // Hint session configuration data + aidl::android::hardware::power::SessionConfig mSessionConfig; + + // Whether createHintSessionWithConfig is supported, assume true until it fails + bool mSessionConfigSupported = true; + bool mFirstConfigSupportCheck = true; + // Whether we should emit ATRACE_INT data for hint sessions static const bool sTraceHintSessionData; diff --git a/services/surfaceflinger/common/include/common/test/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h index 550c70d98f..d61fcb5438 100644 --- a/services/surfaceflinger/common/include/common/test/FlagUtils.h +++ b/services/surfaceflinger/common/include/common/test/FlagUtils.h @@ -18,7 +18,10 @@ #include -#define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value)) +#define SET_FLAG_FOR_TEST(name, value) \ + TestFlagSetter _testflag_ { \ + (name), (name), (value) \ + } namespace android { class TestFlagSetter { diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index 1d44a3ef77..d9343c7813 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -18,7 +18,9 @@ #define LOG_TAG "PowerAdvisorTest" #include +#include #include +#include #include #include #include @@ -55,6 +57,7 @@ protected: std::unique_ptr mPowerAdvisor; MockPowerHalController* mMockPowerHalController; std::shared_ptr mMockPowerHintSession; + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, true); }; bool PowerAdvisorTest::sessionExists() { @@ -75,13 +78,14 @@ void PowerAdvisorTest::SetUp() { void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) { mMockPowerHintSession = std::make_shared>(); if (returnValidSession) { - ON_CALL(*mMockPowerHalController, createHintSession) - .WillByDefault([&](int32_t, int32_t, const std::vector&, int64_t) { - return HalResult>:: - fromStatus(ndk::ScopedAStatus::ok(), mMockPowerHintSession); - }); + ON_CALL(*mMockPowerHalController, createHintSessionWithConfig) + .WillByDefault(DoAll(SetArgPointee<5>(aidl::android::hardware::power::SessionConfig{ + .id = 12}), + Return(HalResult>:: + fromStatus(binder::Status::ok(), + mMockPowerHintSession)))); } else { - ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] { + ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] { return HalResult< std::shared_ptr>::fromStatus(ndk::ScopedAStatus::ok(), nullptr); @@ -287,7 +291,7 @@ TEST_F(PowerAdvisorTest, hintSessionValidWhenNullFromPowerHAL) { } TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) { - EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1); + EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1); mPowerAdvisor->onBootFinished(); startPowerHintSession(); mPowerAdvisor->startPowerHintSession({1, 2, 3}); @@ -339,7 +343,7 @@ TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) { return HalResult::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127)); }); - ON_CALL(*mMockPowerHalController, createHintSession).WillByDefault([] { + ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] { return HalResult>:: fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr); }); @@ -374,5 +378,17 @@ TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) { EXPECT_EQ(sessionExists(), false); } +TEST_F(PowerAdvisorTest, legacyHintSessionCreationStillWorks) { + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, false); + mPowerAdvisor->onBootFinished(); + mMockPowerHintSession = std::make_shared>(); + EXPECT_CALL(*mMockPowerHalController, createHintSession) + .Times(1) + .WillOnce(Return(HalResult>:: + fromStatus(binder::Status::ok(), mMockPowerHintSession))); + mPowerAdvisor->enablePowerHintSession(true); + mPowerAdvisor->startPowerHintSession({1, 2, 3}); +} + } // namespace } // namespace android::Hwc2::impl -- GitLab From 959f5184636c98d1b98ccf7c22a354ef9fb45006 Mon Sep 17 00:00:00 2001 From: Yiwei Zhang Date: Tue, 12 Mar 2024 02:46:36 +0000 Subject: [PATCH 056/465] swapchain: pass VkFormat to GetNativeDataspace directly No need for the extra convert just to workaround bt2020 linear ext mapping. This change also refactors to clean up the format population. Bug: b/328125698 Test: compile Change-Id: Iab875a90be1b240415dc1630dad0790d74c13871 --- vulkan/libvulkan/swapchain.cpp | 89 +++++++++++++++------------------- 1 file changed, 39 insertions(+), 50 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 13141933ac..74d3d9dc64 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -555,8 +555,7 @@ PixelFormat GetNativePixelFormat(VkFormat format) { return native_format; } -DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, - PixelFormat pixelFormat) { +DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, VkFormat format) { switch (colorspace) { case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: return DataSpace::SRGB; @@ -575,7 +574,7 @@ DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, case VK_COLOR_SPACE_BT709_NONLINEAR_EXT: return DataSpace::SRGB; case VK_COLOR_SPACE_BT2020_LINEAR_EXT: - if (pixelFormat == PixelFormat::RGBA_FP16) { + if (format == VK_FORMAT_R16G16B16A16_SFLOAT) { return DataSpace::BT2020_LINEAR_EXTENDED; } else { return DataSpace::BT2020_LINEAR; @@ -764,21 +763,20 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, }; + VkFormat format = VK_FORMAT_UNDEFINED; if (colorspace_ext) { for (VkColorSpaceKHR colorSpace : colorSpaceSupportedByVkEXTSwapchainColorspace) { - if (GetNativeDataspace(colorSpace, GetNativePixelFormat( - VK_FORMAT_R8G8B8A8_UNORM)) != - DataSpace::UNKNOWN) { + format = VK_FORMAT_R8G8B8A8_UNORM; + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, colorSpace}); + VkSurfaceFormatKHR{format, colorSpace}); } - if (GetNativeDataspace(colorSpace, GetNativePixelFormat( - VK_FORMAT_R8G8B8A8_SRGB)) != - DataSpace::UNKNOWN) { + format = VK_FORMAT_R8G8B8A8_SRGB; + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_SRGB, colorSpace}); + VkSurfaceFormatKHR{format, colorSpace}); } } } @@ -787,78 +785,73 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, // Android users. This includes the ANGLE team (a layered implementation of // OpenGL-ES). + format = VK_FORMAT_R5G6B5_UNORM_PACK16; desc.format = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM; if (AHardwareBuffer_isSupported(&desc)) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { for (VkColorSpaceKHR colorSpace : colorSpaceSupportedByVkEXTSwapchainColorspace) { - if (GetNativeDataspace( - colorSpace, - GetNativePixelFormat(VK_FORMAT_R5G6B5_UNORM_PACK16)) != + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, colorSpace}); } } } } + format = VK_FORMAT_R16G16B16A16_SFLOAT; desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT; if (AHardwareBuffer_isSupported(&desc)) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { for (VkColorSpaceKHR colorSpace : colorSpaceSupportedByVkEXTSwapchainColorspace) { - if (GetNativeDataspace( - colorSpace, - GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) != + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, colorSpace}); } } for ( VkColorSpaceKHR colorSpace : colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly) { - if (GetNativeDataspace( - colorSpace, - GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) != + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, colorSpace}); } } } } + format = VK_FORMAT_A2B10G10R10_UNORM_PACK32; desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM; if (AHardwareBuffer_isSupported(&desc)) { all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32, - VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); + VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { for (VkColorSpaceKHR colorSpace : colorSpaceSupportedByVkEXTSwapchainColorspace) { - if (GetNativeDataspace( - colorSpace, GetNativePixelFormat( - VK_FORMAT_A2B10G10R10_UNORM_PACK32)) != + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, colorSpace}); } } } } + format = VK_FORMAT_R8_UNORM; desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM; if (AHardwareBuffer_isSupported(&desc)) { if (colorspace_ext) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, VK_COLOR_SPACE_PASS_THROUGH_EXT}); } } @@ -877,22 +870,18 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev, rgba10x6_formats_ext = true; } } + format = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16; desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM; if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) { all_formats.emplace_back( - VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, - VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); + VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}); if (colorspace_ext) { for (VkColorSpaceKHR colorSpace : colorSpaceSupportedByVkEXTSwapchainColorspace) { - if (GetNativeDataspace( - colorSpace, - GetNativePixelFormat( - VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16)) != + if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) { - all_formats.emplace_back(VkSurfaceFormatKHR{ - VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16, - colorSpace}); + all_formats.emplace_back( + VkSurfaceFormatKHR{format, colorSpace}); } } } @@ -1670,8 +1659,8 @@ VkResult CreateSwapchainKHR(VkDevice device, PixelFormat native_pixel_format = GetNativePixelFormat(create_info->imageFormat); - DataSpace native_dataspace = - GetNativeDataspace(create_info->imageColorSpace, native_pixel_format); + DataSpace native_dataspace = GetNativeDataspace( + create_info->imageColorSpace, create_info->imageFormat); if (native_dataspace == DataSpace::UNKNOWN) { ALOGE( "CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) " -- GitLab From d5b1690f4eb8587c3ec6f6fec5df01a1c10b8c2c Mon Sep 17 00:00:00 2001 From: Yiwei Zhang Date: Mon, 11 Mar 2024 21:35:06 +0000 Subject: [PATCH 057/465] swapchain: support RGBX backed opaque swapchain for offscreen When a swapchain is connected to SurfaceFlinger, it makes sense to only advertise INHERIT bit on Android. However, for offscreen scenarios, e.g. a video encoder surface, that currently shuts the door for preferring RGBX over RGBA for the requested VK_FORMAT_R8G8B8A8_* format, ending up with suboptimal performance when using Vulkan swapchain. This change advertises the OPAQUE bit for offscreen scenarios, and adds the code path to prefer RGBX as the backing storage when OPAQUE is used. This bridges a gap against EGL where GL_RGB8 is used for such encoder surface, which internally picks RGBX. This casts no regressions for anything currently going to the display via KHR_swapchain. Bug: b/328125698, b/328122401 Test: below tests pass with ANGLE on Zork device (AMD chromebook) 1. android.media.encoder.cts.SurfaceEncodeTimestampTest 2. android.media.codec.cts.EncodeDecodeTest - testEncodeDecodeVideoFromSurfaceToPersistentSurface - testEncodeDecodeVideoFromSurfaceToPersistentSurfaceNdk Change-Id: I82a7ac741dc70a3e3b5fdd4f7499c937c4dce6d8 --- vulkan/libvulkan/swapchain.cpp | 40 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 74d3d9dc64..6ba2eb1ad7 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -526,12 +526,15 @@ void copy_ready_timings(Swapchain& swapchain, *count = num_copied; } -PixelFormat GetNativePixelFormat(VkFormat format) { +PixelFormat GetNativePixelFormat(VkFormat format, + VkCompositeAlphaFlagBitsKHR alpha) { PixelFormat native_format = PixelFormat::RGBA_8888; switch (format) { case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R8G8B8A8_SRGB: - native_format = PixelFormat::RGBA_8888; + native_format = alpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR + ? PixelFormat::RGBX_8888 + : PixelFormat::RGBA_8888; break; case VK_FORMAT_R5G6B5_UNORM_PACK16: native_format = PixelFormat::RGB_565; @@ -901,7 +904,7 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { ATRACE_CALL(); - auto surface = pSurfaceInfo->surface; + auto surface_handle = pSurfaceInfo->surface; auto capabilities = &pSurfaceCapabilities->surfaceCapabilities; VkSurfacePresentModeEXT const *pPresentMode = nullptr; @@ -922,7 +925,13 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( int transform_hint; int max_buffer_count; int min_undequeued_buffers; - if (surface == VK_NULL_HANDLE) { + // On Android, window composition is a WindowManager property, not something + // associated with the bufferqueue. It can't be changed from here for a + // swapchain connected with SurfaceFlinger. For offscreen surfaces, it's + // allowed to report opaque being supported for RGBX preference. + VkCompositeAlphaFlagsKHR composite_alpha = + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + if (surface_handle == VK_NULL_HANDLE) { const InstanceData& instance_data = GetData(physicalDevice); ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; bool surfaceless_enabled = @@ -943,7 +952,8 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( capabilities->minImageCount = 0xFFFFFFFF; capabilities->maxImageCount = 0xFFFFFFFF; } else { - ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); + Surface& surface = *SurfaceFromHandle(surface_handle); + ANativeWindow* window = surface.window.get(); err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); if (err != android::OK) { @@ -1018,6 +1028,11 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( min_undequeued_buffers + default_additional_buffers); capabilities->maxImageCount = static_cast(max_buffer_count); } + + if (!(surface.consumer_usage & + AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY)) { + composite_alpha |= VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } } capabilities->currentExtent = @@ -1044,9 +1059,7 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( capabilities->currentTransform = TranslateNativeToVulkanTransform(transform_hint); - // On Android, window composition is a WindowManager property, not something - // associated with the bufferqueue. It can't be changed from here. - capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + capabilities->supportedCompositeAlpha = composite_alpha; capabilities->supportedUsageFlags = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | @@ -1645,20 +1658,21 @@ VkResult CreateSwapchainKHR(VkDevice device, ALOGV("vkCreateSwapchainKHR: surface=0x%" PRIx64 " minImageCount=%u imageFormat=%u imageColorSpace=%u" - " imageExtent=%ux%u imageUsage=%#x preTransform=%u presentMode=%u" - " oldSwapchain=0x%" PRIx64, + " imageExtent=%ux%u imageUsage=%#x preTransform=%u compositeAlpha=%u" + " presentMode=%u oldSwapchain=0x%" PRIx64, reinterpret_cast(create_info->surface), create_info->minImageCount, create_info->imageFormat, create_info->imageColorSpace, create_info->imageExtent.width, create_info->imageExtent.height, create_info->imageUsage, - create_info->preTransform, create_info->presentMode, + create_info->preTransform, create_info->compositeAlpha, + create_info->presentMode, reinterpret_cast(create_info->oldSwapchain)); if (!allocator) allocator = &GetData(device).allocator; - PixelFormat native_pixel_format = - GetNativePixelFormat(create_info->imageFormat); + PixelFormat native_pixel_format = GetNativePixelFormat( + create_info->imageFormat, create_info->compositeAlpha); DataSpace native_dataspace = GetNativeDataspace( create_info->imageColorSpace, create_info->imageFormat); if (native_dataspace == DataSpace::UNKNOWN) { -- GitLab From 1ce0f4aab8ce0b37cd9cf0eeb90c0e4d4a7b7c40 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 4 Mar 2024 19:38:12 +0000 Subject: [PATCH 058/465] Revert "SF: Introduce VsyncTimeline to VsyncPredictor" This reverts commit b6c7f880460c81a6ce49ccb3334e2d2e1e020f81. Reason for revert: Regressions tracked as childs on b/326599221 Change-Id: Ic0f959113a2d434d3b6412c90b58b85e5151e436 Merged-In: Ic0f959113a2d434d3b6412c90b58b85e5151e436 (cherry picked from commit 9633f8e8902ab50891e8a825b850fbea74a9a21d) --- .../Scheduler/VSyncDispatchTimerQueue.cpp | 71 ++--- .../Scheduler/VSyncDispatchTimerQueue.h | 4 +- .../Scheduler/VSyncPredictor.cpp | 298 +++++------------- .../surfaceflinger/Scheduler/VSyncPredictor.h | 60 +--- .../surfaceflinger/Scheduler/VSyncTracker.h | 6 +- .../Scheduler/VsyncSchedule.cpp | 4 +- .../surfaceflinger/Scheduler/VsyncSchedule.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 12 +- .../tests/unittests/SchedulerTest.cpp | 11 +- .../unittests/VSyncDispatchRealtimeTest.cpp | 6 +- .../tests/unittests/VSyncPredictorTest.cpp | 101 ++---- .../tests/unittests/mock/MockVSyncTracker.h | 4 +- 12 files changed, 171 insertions(+), 408 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp index 6d6b70d198..84ccf8e938 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -44,17 +44,6 @@ ScheduleResult getExpectedCallbackTime(nsecs_t nextVsyncTime, TimePoint::fromNs(nextVsyncTime)}; } -void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) { - if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) { - return; - } - - ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ", - ns2us(*entry.wakeupTime() - now), "us; VSYNC in ", - ns2us(*entry.targetVsync() - now), "us"); - ATRACE_FORMAT_INSTANT(trace.c_str()); -} - } // namespace VSyncDispatch::~VSyncDispatch() = default; @@ -98,7 +87,6 @@ std::optional VSyncDispatchTimerQueueEntry::targetVsync() const { ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing, VSyncTracker& tracker, nsecs_t now) { - ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule"); auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync, now + timing.workDuration + @@ -110,8 +98,6 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance)); bool const wouldSkipAWakeup = mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance))); - ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), - wouldSkipAVsyncTarget, wouldSkipAWakeup); if (FlagManager::getInstance().dont_skip_on_early_ro()) { if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { nextVsyncTime = mArmedInfo->mActualVsyncTime; @@ -136,7 +122,7 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim ScheduleResult VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate( VSyncTracker& tracker, nsecs_t now, VSyncDispatch::ScheduleTiming timing) { mWorkloadUpdateInfo = timing; - const auto armedInfo = getArmedInfo(tracker, now, timing, mArmedInfo); + const auto armedInfo = update(tracker, now, timing, mArmedInfo); return {TimePoint::fromNs(armedInfo.mActualWakeupTime), TimePoint::fromNs(armedInfo.mActualVsyncTime)}; } @@ -154,13 +140,11 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, bool const nextVsyncTooClose = mLastDispatchTime && (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod; if (alreadyDispatchedForVsync) { - ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance, *mLastDispatchTime); } if (nextVsyncTooClose) { - ATRACE_FORMAT_INSTANT("nextVsyncTooClose"); return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod, *mLastDispatchTime + currentPeriod); } @@ -168,11 +152,9 @@ nsecs_t VSyncDispatchTimerQueueEntry::adjustVsyncIfNeeded(VSyncTracker& tracker, return nextVsyncTime; } -auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t now, - VSyncDispatch::ScheduleTiming timing, - std::optional armedInfo) const - -> ArmingInfo { - ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo"); +auto VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now, + VSyncDispatch::ScheduleTiming timing, + std::optional armedInfo) const -> ArmingInfo { const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration; const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync); @@ -183,39 +165,29 @@ auto VSyncDispatchTimerQueueEntry::getArmedInfo(VSyncTracker& tracker, nsecs_t n const auto nextReadyTime = nextVsyncTime - timing.readyDuration; const auto nextWakeupTime = nextReadyTime - timing.workDuration; - if (FlagManager::getInstance().dont_skip_on_early_ro()) { - bool const wouldSkipAVsyncTarget = - armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); - bool const wouldSkipAWakeup = - armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); - ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(), - wouldSkipAVsyncTarget, wouldSkipAWakeup); - if (wouldSkipAVsyncTarget || wouldSkipAWakeup) { - return *armedInfo; - } + bool const wouldSkipAVsyncTarget = + armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance)); + bool const wouldSkipAWakeup = + armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance)); + if (FlagManager::getInstance().dont_skip_on_early_ro() && + (wouldSkipAVsyncTarget || wouldSkipAWakeup)) { + return *armedInfo; } return ArmingInfo{nextWakeupTime, nextVsyncTime, nextReadyTime}; } void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) { - ATRACE_NAME("VSyncDispatchTimerQueueEntry::update"); if (!mArmedInfo && !mWorkloadUpdateInfo) { return; } if (mWorkloadUpdateInfo) { - const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration; - const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration; - const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync; - ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64 - " lastVsyncDelta=%" PRId64, - workDelta, readyDelta, lastVsyncDelta); mScheduleTiming = *mWorkloadUpdateInfo; mWorkloadUpdateInfo.reset(); } - mArmedInfo = getArmedInfo(tracker, now, mScheduleTiming, mArmedInfo); + mArmedInfo = update(tracker, now, mScheduleTiming, mArmedInfo); } void VSyncDispatchTimerQueueEntry::disarm() { @@ -310,7 +282,6 @@ void VSyncDispatchTimerQueue::rearmTimer(nsecs_t now) { void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( nsecs_t now, CallbackMap::const_iterator skipUpdateIt) { - ATRACE_CALL(); std::optional min; std::optional targetVsync; std::optional nextWakeupName; @@ -323,10 +294,7 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( if (it != skipUpdateIt) { callback->update(*mTracker, now); } - - traceEntry(*callback, now); - - const auto wakeupTime = *callback->wakeupTime(); + auto const wakeupTime = *callback->wakeupTime(); if (!min || *min > wakeupTime) { nextWakeupName = callback->name(); min = wakeupTime; @@ -335,6 +303,11 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } if (min && min < mIntendedWakeupTime) { + if (ATRACE_ENABLED() && nextWakeupName && targetVsync) { + ftl::Concat trace(ftl::truncated<5>(*nextWakeupName), " alarm in ", ns2us(*min - now), + "us; VSYNC in ", ns2us(*targetVsync - now), "us"); + ATRACE_NAME(trace.c_str()); + } setTimer(*min, now); } else { ATRACE_NAME("cancel timer"); @@ -343,7 +316,6 @@ void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor( } void VSyncDispatchTimerQueue::timerCallback() { - ATRACE_CALL(); struct Invocation { std::shared_ptr callback; nsecs_t vsyncTimestamp; @@ -366,9 +338,8 @@ void VSyncDispatchTimerQueue::timerCallback() { continue; } - traceEntry(*callback, now); - auto const readyTime = callback->readyTime(); + auto const lagAllowance = std::max(now - mIntendedWakeupTime, static_cast(0)); if (*wakeupTime < mIntendedWakeupTime + mTimerSlack + lagAllowance) { callback->executing(); @@ -382,8 +353,6 @@ void VSyncDispatchTimerQueue::timerCallback() { } for (auto const& invocation : invocations) { - ftl::Concat trace(ftl::truncated<5>(invocation.callback->name())); - ATRACE_FORMAT("%s: %s", __func__, trace.c_str()); invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp, invocation.deadlineTimestamp); } diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h index e4ddc03480..252c09ce53 100644 --- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h +++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h @@ -91,8 +91,8 @@ private: }; nsecs_t adjustVsyncIfNeeded(VSyncTracker& tracker, nsecs_t nextVsyncTime) const; - ArmingInfo getArmedInfo(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, - std::optional) const; + ArmingInfo update(VSyncTracker&, nsecs_t now, VSyncDispatch::ScheduleTiming, + std::optional) const; const std::string mName; const VSyncDispatch::Callback mCallback; diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 2f9dfeaff7..8697696915 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -45,35 +45,11 @@ using base::StringAppendF; static auto constexpr kMaxPercent = 100u; -namespace { -nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime, - std::optional lastVsyncOpt) { - const auto threshold = model.slope / 2; - - if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { - const auto vsyncDiff = vsyncTime - *lastVsyncOpt; - if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) { - const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime; - ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. " - "adjust by %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, - static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, - static_cast(vsyncFixup) / 1e6f); - return vsyncFixup; - } - } - - return 0; -} -} // namespace - VSyncPredictor::~VSyncPredictor() = default; -VSyncPredictor::VSyncPredictor(std::unique_ptr clock, ftl::NonNull modePtr, - size_t historySize, size_t minimumSamplesForPrediction, - uint32_t outlierTolerancePercent) - : mClock(std::move(clock)), - mId(modePtr->getPhysicalDisplayId()), +VSyncPredictor::VSyncPredictor(ftl::NonNull modePtr, size_t historySize, + size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent) + : mId(modePtr->getPhysicalDisplayId()), mTraceOn(property_get_bool("debug.sf.vsp_trace", false)), kHistorySize(historySize), kMinimumSamplesForPrediction(minimumSamplesForPrediction), @@ -171,7 +147,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { mKnownTimestamp = timestamp; } ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago", - (mClock->now() - *mKnownTimestamp) / 1e6f); + (systemTime() - *mKnownTimestamp) / 1e6f); return false; } @@ -274,6 +250,17 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) { return true; } +auto VSyncPredictor::getVsyncSequenceLocked(nsecs_t timestamp) const -> VsyncSequence { + const auto vsync = snapToVsync(timestamp); + if (!mLastVsyncSequence) return {vsync, 0}; + + const auto [slope, _] = getVSyncPredictionModelLocked(); + const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; + const auto vsyncSequence = lastVsyncSequence + + static_cast(std::round((vsync - lastVsyncTime) / static_cast(slope))); + return {vsync, vsyncSequence}; +} + nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { auto const [slope, intercept] = getVSyncPredictionModelLocked(); @@ -311,32 +298,51 @@ nsecs_t VSyncPredictor::snapToVsync(nsecs_t timePoint) const { } nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt) { + std::optional lastVsyncOpt) const { ATRACE_CALL(); std::lock_guard lock(mMutex); + const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; + const auto threshold = currentPeriod / 2; + const auto minFramePeriod = minFramePeriodLocked().ns(); + const auto lastFrameMissed = + lastVsyncOpt && std::abs(*lastVsyncOpt - mLastMissedVsync.ns()) < threshold; + const nsecs_t baseTime = + FlagManager::getInstance().vrr_config() && !lastFrameMissed && lastVsyncOpt + ? std::max(timePoint, *lastVsyncOpt + minFramePeriod - threshold) + : timePoint; + return snapToVsyncAlignedWithRenderRate(baseTime); +} - const auto now = TimePoint::fromNs(mClock->now()); - purgeTimelines(now); +nsecs_t VSyncPredictor::snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const { + // update the mLastVsyncSequence for reference point + mLastVsyncSequence = getVsyncSequenceLocked(timePoint); - std::optional vsyncOpt; - for (auto& timeline : mTimelines) { - vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(), - minFramePeriodLocked(), - snapToVsync(timePoint), mMissedVsync, - lastVsyncOpt); - if (vsyncOpt) { - break; + const auto renderRatePhase = [&]() REQUIRES(mMutex) -> int { + if (!mRenderRateOpt) return 0; + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), + *mRenderRateOpt); + if (divisor <= 1) return 0; + + int mod = mLastVsyncSequence->seq % divisor; + if (mod == 0) return 0; + + // This is actually a bug fix, but guarded with vrr_config since we found it with this + // config + if (FlagManager::getInstance().vrr_config()) { + if (mod < 0) mod += divisor; } - } - LOG_ALWAYS_FATAL_IF(!vsyncOpt); - if (*vsyncOpt > mLastCommittedVsync) { - mLastCommittedVsync = *vsyncOpt; - ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms", - float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f); + return divisor - mod; + }(); + + if (renderRatePhase == 0) { + return mLastVsyncSequence->vsyncTime; } - return vsyncOpt->ns(); + auto const [slope, intercept] = getVSyncPredictionModelLocked(); + const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase; + return snapToVsync(approximateNextVsync - slope / 2); } /* @@ -347,28 +353,32 @@ nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, * isVSyncInPhase(33.3, 30) = false * isVSyncInPhase(50.0, 30) = true */ -bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) { - if (timePoint == 0) { - return true; - } - +bool VSyncPredictor::isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const { std::lock_guard lock(mMutex); - const auto model = getVSyncPredictionModelLocked(); - const nsecs_t period = model.slope; - const nsecs_t justBeforeTimePoint = timePoint - period / 2; - const auto now = TimePoint::fromNs(mClock->now()); - const auto vsync = snapToVsync(justBeforeTimePoint); + const auto divisor = + RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()), + frameRate); + return isVSyncInPhaseLocked(timePoint, static_cast(divisor)); +} - purgeTimelines(now); +bool VSyncPredictor::isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const { + const TimePoint now = TimePoint::now(); + const auto getTimePointIn = [](TimePoint now, nsecs_t timePoint) -> float { + return ticks(TimePoint::fromNs(timePoint) - now); + }; + ATRACE_FORMAT("%s timePoint in: %.2f divisor: %zu", __func__, getTimePointIn(now, timePoint), + divisor); - for (auto& timeline : mTimelines) { - if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) { - return timeline.isVSyncInPhase(model, vsync, frameRate); - } + if (divisor <= 1 || timePoint == 0) { + return true; } - // The last timeline should always be valid - return mTimelines.back().isVSyncInPhase(model, vsync, frameRate); + const nsecs_t period = mRateMap[idealPeriod()].slope; + const nsecs_t justBeforeTimePoint = timePoint - period / 2; + const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint); + ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64, + getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq); + return vsyncSequence.seq % divisor == 0; } void VSyncPredictor::setRenderRate(Fps renderRate) { @@ -376,9 +386,6 @@ void VSyncPredictor::setRenderRate(Fps renderRate) { ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str()); std::lock_guard lock(mMutex); mRenderRateOpt = renderRate; - mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2)); - mTimelines.emplace_back(mIdealPeriod, renderRate); - purgeTimelines(TimePoint::fromNs(mClock->now())); } void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { @@ -408,9 +415,8 @@ void VSyncPredictor::setDisplayModePtr(ftl::NonNull modePtr) { clearTimestamps(); } -Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, - TimePoint lastConfirmedPresentTime) { - ATRACE_CALL(); +void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime, + TimePoint lastConfirmedPresentTime) { const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope; const auto threshold = currentPeriod / 2; const auto minFramePeriod = minFramePeriodLocked().ns(); @@ -436,20 +442,17 @@ Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentT if (!mPastExpectedPresentTimes.empty()) { const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime); if (phase > 0ns) { - for (auto& timeline : mTimelines) { - timeline.shiftVsyncSequence(phase); + if (mLastVsyncSequence) { + mLastVsyncSequence->vsyncTime += phase.ns(); } mPastExpectedPresentTimes.clear(); - return phase; } } - - return 0ns; } void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) { - ATRACE_NAME("VSyncPredictor::onFrameBegin"); + ATRACE_CALL(); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -479,14 +482,11 @@ void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, } } - const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); - if (phase > 0ns) { - mMissedVsync = {expectedPresentTime, minFramePeriodLocked()}; - } + ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); } void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { - ATRACE_NAME("VSyncPredictor::onFrameMissed"); + ATRACE_CALL(); std::lock_guard lock(mMutex); if (!mDisplayModePtr->getVrrConfig()) return; @@ -496,15 +496,14 @@ void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) { const auto lastConfirmedPresentTime = TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod); - const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); - if (phase > 0ns) { - mMissedVsync = {expectedPresentTime, Duration::fromNs(0)}; - } + ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime); + mLastMissedVsync = expectedPresentTime; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const { std::lock_guard lock(mMutex); - return VSyncPredictor::getVSyncPredictionModelLocked(); + const auto model = VSyncPredictor::getVSyncPredictionModelLocked(); + return {model.slope, model.intercept}; } VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const { @@ -525,11 +524,6 @@ void VSyncPredictor::clearTimestamps() { mTimestamps.clear(); mLastTimestampIndex = 0; } - - mTimelines.clear(); - mLastCommittedVsync = TimePoint::fromNs(0); - mIdealPeriod = Period::fromNs(idealPeriod()); - mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt); } bool VSyncPredictor::needsMoreSamples() const { @@ -553,130 +547,6 @@ void VSyncPredictor::dump(std::string& result) const { period / 1e6f, periodInterceptTuple.slope / 1e6f, periodInterceptTuple.intercept); } - StringAppendF(&result, "\tmTimelines.size()=%zu\n", mTimelines.size()); -} - -void VSyncPredictor::purgeTimelines(android::TimePoint now) { - while (mTimelines.size() > 1) { - const auto validUntilOpt = mTimelines.front().validUntil(); - if (validUntilOpt && *validUntilOpt < now) { - mTimelines.pop_front(); - } else { - break; - } - } - LOG_ALWAYS_FATAL_IF(mTimelines.empty()); - LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value()); -} - -VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional renderRateOpt) - : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {} - -void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) { - LOG_ALWAYS_FATAL_IF(mValidUntil.has_value()); - ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f", - mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA", - float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f); - mValidUntil = lastVsync; -} - -std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom( - Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync, - std::optional lastVsyncOpt) { - ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA"); - - const auto threshold = model.slope / 2; - const auto lastFrameMissed = - lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; - nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync); - nsecs_t vsyncFixupTime = 0; - if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { - vsyncTime += missedVsync.fixup.ns(); - ATRACE_FORMAT_INSTANT("lastFrameMissed"); - } else { - vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt); - vsyncTime += vsyncFixupTime; - } - - ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f); - if (mValidUntil && vsyncTime > mValidUntil->ns()) { - ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f); - return std::nullopt; - } - - if (vsyncFixupTime > 0) { - shiftVsyncSequence(Duration::fromNs(vsyncFixupTime)); - } - - return TimePoint::fromNs(vsyncTime); -} - -auto VSyncPredictor::VsyncTimeline::getVsyncSequenceLocked(Model model, nsecs_t vsync) - -> VsyncSequence { - if (!mLastVsyncSequence) return {vsync, 0}; - - const auto [lastVsyncTime, lastVsyncSequence] = *mLastVsyncSequence; - const auto vsyncSequence = lastVsyncSequence + - static_cast(std::round((vsync - lastVsyncTime) / - static_cast(model.slope))); - return {vsync, vsyncSequence}; -} - -nsecs_t VSyncPredictor::VsyncTimeline::snapToVsyncAlignedWithRenderRate(Model model, - nsecs_t vsync) { - // update the mLastVsyncSequence for reference point - mLastVsyncSequence = getVsyncSequenceLocked(model, vsync); - - const auto renderRatePhase = [&]() -> int { - if (!mRenderRateOpt) return 0; - const auto divisor = - RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(mIdealPeriod.ns()), - *mRenderRateOpt); - if (divisor <= 1) return 0; - - int mod = mLastVsyncSequence->seq % divisor; - if (mod == 0) return 0; - - // This is actually a bug fix, but guarded with vrr_config since we found it with this - // config - if (FlagManager::getInstance().vrr_config()) { - if (mod < 0) mod += divisor; - } - - return divisor - mod; - }(); - - if (renderRatePhase == 0) { - return mLastVsyncSequence->vsyncTime; - } - - return mLastVsyncSequence->vsyncTime + model.slope * renderRatePhase; -} - -bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, Fps frameRate) { - const auto getVsyncIn = [](TimePoint now, nsecs_t timePoint) -> float { - return ticks(TimePoint::fromNs(timePoint) - now); - }; - - Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns()); - const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate); - const auto now = TimePoint::now(); - - if (divisor <= 1) { - return true; - } - const auto vsyncSequence = getVsyncSequenceLocked(model, vsync); - ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu", - getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor); - return vsyncSequence.seq % divisor == 0; -} - -void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) { - if (mLastVsyncSequence) { - ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast(phase.ns()) / 1e6f); - mLastVsyncSequence->vsyncTime += phase.ns(); - } } } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h index c175765485..8fd7e6046d 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.h +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h @@ -22,7 +22,6 @@ #include #include -#include #include #include "VSyncTracker.h" @@ -32,7 +31,6 @@ namespace android::scheduler { class VSyncPredictor : public VSyncTracker { public: /* - * \param [in] Clock The clock abstraction. Useful for unit tests. * \param [in] PhysicalDisplayid The display this corresponds to. * \param [in] modePtr The initial display mode * \param [in] historySize The internal amount of entries to store in the model. @@ -40,13 +38,13 @@ public: * predicting. \param [in] outlierTolerancePercent a number 0 to 100 that will be used to filter * samples that fall outlierTolerancePercent from an anticipated vsync event. */ - VSyncPredictor(std::unique_ptr, ftl::NonNull modePtr, size_t historySize, + VSyncPredictor(ftl::NonNull modePtr, size_t historySize, size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent); ~VSyncPredictor(); bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex); nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt = {}) final + std::optional lastVsyncOpt = {}) const final EXCLUDES(mMutex); nsecs_t currentPeriod() const final EXCLUDES(mMutex); Period minFramePeriod() const final EXCLUDES(mMutex); @@ -64,7 +62,7 @@ public: VSyncPredictor::Model getVSyncPredictionModel() const EXCLUDES(mMutex); - bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) final EXCLUDES(mMutex); + bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex); void setDisplayModePtr(ftl::NonNull) final EXCLUDES(mMutex); @@ -77,42 +75,10 @@ public: void dump(std::string& result) const final EXCLUDES(mMutex); private: - struct VsyncSequence { - nsecs_t vsyncTime; - int64_t seq; - }; - - struct MissedVsync { - TimePoint vsync; - Duration fixup = Duration::fromNs(0); - }; - - class VsyncTimeline { - public: - VsyncTimeline(Period idealPeriod, std::optional renderRateOpt); - std::optional nextAnticipatedVSyncTimeFrom( - Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync, - std::optional lastVsyncOpt = {}); - void freeze(TimePoint lastVsync); - std::optional validUntil() const { return mValidUntil; } - bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate); - void shiftVsyncSequence(Duration phase); - - private: - nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync); - VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync); - - const Period mIdealPeriod = Duration::fromNs(0); - const std::optional mRenderRateOpt; - std::optional mValidUntil; - std::optional mLastVsyncSequence; - }; - VSyncPredictor(VSyncPredictor const&) = delete; VSyncPredictor& operator=(VSyncPredictor const&) = delete; void clearTimestamps() REQUIRES(mMutex); - const std::unique_ptr mClock; const PhysicalDisplayId mId; inline void traceInt64If(const char* name, int64_t value) const; @@ -122,10 +88,16 @@ private: bool validate(nsecs_t timestamp) const REQUIRES(mMutex); Model getVSyncPredictionModelLocked() const REQUIRES(mMutex); nsecs_t snapToVsync(nsecs_t timePoint) const REQUIRES(mMutex); + nsecs_t snapToVsyncAlignedWithRenderRate(nsecs_t timePoint) const REQUIRES(mMutex); + bool isVSyncInPhaseLocked(nsecs_t timePoint, unsigned divisor) const REQUIRES(mMutex); Period minFramePeriodLocked() const REQUIRES(mMutex); - Duration ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); - void purgeTimelines(android::TimePoint now) REQUIRES(mMutex); + void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex); + struct VsyncSequence { + nsecs_t vsyncTime; + int64_t seq; + }; + VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex); nsecs_t idealPeriod() const REQUIRES(mMutex); bool const mTraceOn; @@ -143,15 +115,13 @@ private: std::vector mTimestamps GUARDED_BY(mMutex); ftl::NonNull mDisplayModePtr GUARDED_BY(mMutex); + std::optional mRenderRateOpt GUARDED_BY(mMutex); - std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); + mutable std::optional mLastVsyncSequence GUARDED_BY(mMutex); - MissedVsync mMissedVsync GUARDED_BY(mMutex); + std::deque mPastExpectedPresentTimes GUARDED_BY(mMutex); - std::deque mTimelines GUARDED_BY(mMutex); - TimePoint mLastCommittedVsync GUARDED_BY(mMutex) = TimePoint::fromNs(0); - Period mIdealPeriod GUARDED_BY(mMutex) = Duration::fromNs(0); - std::optional mRenderRateOpt GUARDED_BY(mMutex); + TimePoint mLastMissedVsync GUARDED_BY(mMutex); }; } // namespace android::scheduler diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h index 1e55a87254..37bd4b4977 100644 --- a/services/surfaceflinger/Scheduler/VSyncTracker.h +++ b/services/surfaceflinger/Scheduler/VSyncTracker.h @@ -56,8 +56,8 @@ public: * and avoid crossing the minimal frame period of a VRR display. * \return A prediction of the timestamp of a vsync event. */ - virtual nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, - std::optional lastVsyncOpt = {}) = 0; + virtual nsecs_t nextAnticipatedVSyncTimeFrom( + nsecs_t timePoint, std::optional lastVsyncOpt = {}) const = 0; /* * The current period of the vsync signal. @@ -82,7 +82,7 @@ public: * \param [in] timePoint A vsync timestamp * \param [in] frameRate The frame rate to check for */ - virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) = 0; + virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0; /* * Sets the active mode of the display which includes the vsync period and other VRR attributes. diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp index 2fa3318560..001938c756 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp @@ -120,8 +120,8 @@ VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull(std::make_unique(), modePtr, kHistorySize, - kMinSamplesForPrediction, kDiscardOutlierPercent); + return std::make_unique(modePtr, kHistorySize, kMinSamplesForPrediction, + kDiscardOutlierPercent); } VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(TrackerPtr tracker) { diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h index 881d6789b2..85cd3e7c31 100644 --- a/services/surfaceflinger/Scheduler/VsyncSchedule.h +++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h @@ -81,7 +81,7 @@ public: bool addResyncSample(TimePoint timestamp, ftl::Optional hwcVsyncPeriod); // TODO(b/185535769): Hide behind API. - VsyncTracker& getTracker() const { return *mTracker; } + const VsyncTracker& getTracker() const { return *mTracker; } VsyncTracker& getTracker() { return *mTracker; } VsyncController& getController() { return *mController; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9e131447a5..cf5f55d7bd 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4702,14 +4702,7 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin return TransactionReadiness::NotReady; } - const auto vsyncId = VsyncId{transaction.frameTimelineInfo.vsyncId}; - - // Transactions with VsyncId are already throttled by the vsyncId (i.e. Choreographer issued - // the vsyncId according to the frame rate override cadence) so we shouldn't throttle again - // when applying the transaction. Otherwise we might throttle older transactions - // incorrectly as the frame rate of SF changed before it drained the older transactions. - if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID && - !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { + if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) { ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime, transaction.originUid); return TransactionReadiness::NotReady; @@ -4717,7 +4710,8 @@ TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyTimelin // If the client didn't specify desiredPresentTime, use the vsyncId to determine the // expected present time of this transaction. - if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) { + if (transaction.isAutoTimestamp && + frameIsEarly(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) { ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64, transaction.frameTimelineInfo.vsyncId, expectedPresentTime); return TransactionReadiness::NotReady; diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 049b0923a6..10e2220ece 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -25,7 +25,6 @@ #include "Scheduler/EventThread.h" #include "Scheduler/RefreshRateSelector.h" #include "Scheduler/VSyncPredictor.h" -#include "Scheduler/VSyncReactor.h" #include "TestableScheduler.h" #include "TestableSurfaceFlinger.h" #include "mock/DisplayHardware/MockDisplayMode.h" @@ -564,8 +563,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { hal::VrrConfig{.minFrameIntervalNs = static_cast( frameRate.getPeriodNsecs())})); std::shared_ptr vrrTracker = - std::make_shared(std::make_unique(), kMode, kHistorySize, - kMinimumSamplesForPrediction, + std::make_shared(kMode, kHistorySize, kMinimumSamplesForPrediction, kOutlierTolerancePercent); std::shared_ptr vrrSelectorPtr = std::make_shared(makeModes(kMode), kMode->getId()); @@ -580,8 +578,6 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); vrrTracker->addVsyncTimestamp(0); - // Set 1000 as vsync seq #0 - vrrTracker->nextAnticipatedVSyncTimeFrom(700); EXPECT_EQ(Fps::fromPeriodNsecs(1000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), @@ -591,7 +587,7 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { TimePoint::fromNs(2000))); // Not crossing the min frame period - EXPECT_EQ(Fps::fromPeriodNsecs(1000), + EXPECT_EQ(Fps::fromPeriodNsecs(1500), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2500))); // Change render rate @@ -599,9 +595,6 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate); - // Set 2000 as vsync seq #0 - vrrTracker->nextAnticipatedVSyncTimeFrom(1700); - EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(2000))); diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp index c22deaba72..d891008683 100644 --- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp @@ -48,7 +48,7 @@ public: Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); } void resetModel() final {} bool needsMoreSamples() const final { return false; } - bool isVSyncInPhase(nsecs_t, Fps) final { return false; } + bool isVSyncInPhase(nsecs_t, Fps) const final { return false; } void setDisplayModePtr(ftl::NonNull) final {} void setRenderRate(Fps) final {} void onFrameBegin(TimePoint, TimePoint) final {} @@ -64,7 +64,7 @@ class FixedRateIdealStubTracker : public StubTracker { public: FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint, std::optional) const final { auto const floor = timePoint % mPeriod; if (floor == 0) { return timePoint; @@ -77,7 +77,7 @@ class VRRStubTracker : public StubTracker { public: VRRStubTracker(nsecs_t period) : StubTracker(period) {} - nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) final { + nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point, std::optional) const final { std::lock_guard lock(mMutex); auto const normalized_to_base = time_point - mBase; auto const floor = (normalized_to_base) % mPeriod; diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 6b9ea56062..b9f3d70c6b 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -75,28 +75,6 @@ ftl::NonNull displayMode(nsecs_t period) { return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution, DEFAULT_DISPLAY_ID)); } - -class TestClock : public Clock { -public: - TestClock() = default; - - nsecs_t now() const override { return mNow; } - void setNow(nsecs_t now) { mNow = now; } - -private: - nsecs_t mNow = 0; -}; - -class ClockWrapper : public Clock { -public: - ClockWrapper(std::shared_ptr const& clock) : mClock(clock) {} - - nsecs_t now() const { return mClock->now(); } - -private: - std::shared_ptr const mClock; -}; - } // namespace struct VSyncPredictorTest : testing::Test { @@ -108,10 +86,8 @@ struct VSyncPredictorTest : testing::Test { static constexpr size_t kOutlierTolerancePercent = 25; static constexpr nsecs_t mMaxRoundingError = 100; - std::shared_ptr mClock{std::make_shared()}; - - VSyncPredictor tracker{std::make_unique(mClock), mMode, kHistorySize, - kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction, + kOutlierTolerancePercent}; }; TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) { @@ -432,8 +408,7 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) { // See b/151146131 TEST_F(VSyncPredictorTest, hasEnoughPrecision) { const auto mode = displayMode(mPeriod); - VSyncPredictor tracker{std::make_unique(mClock), mode, 20, - kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent}; std::vector const simulatedVsyncs{840873348817, 840890049444, 840906762675, 840923581635, 840940161584, 840956868096, 840973702473, 840990256277, 841007116851, @@ -631,6 +606,35 @@ TEST_F(VSyncPredictorTest, setRenderRateIsRespected) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod)); } +TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) { + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + const auto refreshRate = Fps::fromPeriodNsecs(mPeriod); + + tracker.setRenderRate(refreshRate / 4); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 2); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod)); + + tracker.setRenderRate(refreshRate / 6); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod)); +} + TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { @@ -666,8 +670,8 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { .setVrrConfig(std::move(vrrConfig)) .build()); - VSyncPredictor vrrTracker{std::make_unique(mClock), kMode, kHistorySize, - kMinimumSamplesForPrediction, kOutlierTolerancePercent}; + VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction, + kOutlierTolerancePercent}; vrrTracker.setRenderRate(minFrameRate); vrrTracker.addVsyncTimestamp(0); @@ -683,44 +687,7 @@ TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { vrrTracker.onFrameMissed(TimePoint::fromNs(4500)); EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500)); EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); - - vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500)); - EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000)); -} - -TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { - tracker.addVsyncTimestamp(1000); - - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - - tracker.setRenderRate(Fps::fromPeriodNsecs(2000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); - - tracker.setRenderRate(Fps::fromPeriodNsecs(3000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000)); - - // Check the purge logic works - mClock->setNow(20000); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(2000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(8000)); - EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000)); } - } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h index 6d10a5cc5d..3870983133 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h +++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h @@ -29,12 +29,12 @@ public: MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override)); MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t, std::optional), - (override)); + (const, override)); MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override)); MOCK_METHOD(Period, minFramePeriod, (), (const, override)); MOCK_METHOD(void, resetModel, (), (override)); MOCK_METHOD(bool, needsMoreSamples, (), (const, override)); - MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override)); + MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override)); MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull), (override)); MOCK_METHOD(void, setRenderRate, (Fps), (override)); MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override)); -- GitLab From 51a1ef60e6791270ff84f63a0550099c66cda66a Mon Sep 17 00:00:00 2001 From: Oriol Prieto Gasco Date: Fri, 1 Mar 2024 16:52:42 +0000 Subject: [PATCH 059/465] Export flags used in FlaggedApi annotations Before calling a flagged API, client code must check the value of the flag which gates it. Those flags must be exported in order to be accessible from containers other than the container where the flag and the API are hosted. Bug: 320984775 Bug: 322839671 Test: m all_aconfig_declarations Test: printflags --format='{fully_qualified_name}:{is_exported}' | grep true Ignore-AOSP-First: LSC Change-Id: I9ab6c048b4787baf121b26753d9bcbe21fb47244 --- libs/input/input_flags.aconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index bdec5c33cd..b48b0fb924 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -87,6 +87,7 @@ flag { flag { name: "override_key_behavior_permission_apis" + is_exported: true namespace: "input" description: "enable override key behavior permission APIs" bug: "309018874" @@ -115,6 +116,7 @@ flag { flag { name: "input_device_view_behavior_api" + is_exported: true namespace: "input" description: "Controls the API to provide InputDevice view behavior." bug: "246946631" -- GitLab From d8edec0686ea3a7842703db1ee108ea1991a8a34 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Mon, 11 Mar 2024 18:14:48 -0500 Subject: [PATCH 060/465] Trigger composition when FLAG_SECURE changes Trigger a visibility change when the FLAG_SECURE value for a layer changes. Bug: 309153458 Test: LayerLifecycleManagerTest Change-Id: I467ea62d5f56f548edef2e85ec7573f2acd805cc --- .../FrontEnd/RequestedLayerState.cpp | 4 ++- .../unittests/LayerLifecycleManagerTest.cpp | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index cb0e2a1938..867f3af4b1 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -163,7 +163,9 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges); if (clientState.what & layer_state_t::eFlagsChanged) { - if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) { + if ((oldFlags ^ flags) & + (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque | + layer_state_t::eLayerSecure)) { changes |= RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::VisibleRegion; } diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index aecfcba6e6..867ff55632 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -560,4 +560,36 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags().string()); } +TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) { + // add a default buffer and make the layer secure + setFlags(1, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); + setBuffer(1, + std::make_shared(1U /*width*/, 1U /*height*/, + 1ULL /* bufferId */, + HAL_PIXEL_FORMAT_RGBA_8888, + GRALLOC_USAGE_SW_READ_NEVER /*usage*/)); + + mLifecycleManager.commitChanges(); + + // set new buffer but layer secure doesn't change + setBuffer(1, + std::make_shared(1U /*width*/, 1U /*height*/, + 2ULL /* bufferId */, + HAL_PIXEL_FORMAT_RGBA_8888, + GRALLOC_USAGE_SW_READ_NEVER /*usage*/)); + EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(), + ftl::Flags(RequestedLayerState::Changes::Buffer | + RequestedLayerState::Changes::Content) + .get()); + mLifecycleManager.commitChanges(); + + // change layer flags and confirm visibility flag is set + setFlags(1, layer_state_t::eLayerSecure, 0); + EXPECT_TRUE( + mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility)); + mLifecycleManager.commitChanges(); +} + } // namespace android::surfaceflinger::frontend -- GitLab From c949cdee55234f377debc2e8bc2193b1d7515742 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Tue, 12 Mar 2024 01:43:34 +0000 Subject: [PATCH 061/465] Clean up uses of refreshStartTime for stats. refreshStartTime is keeps track of frame refreshes for stats and can be set from SurfaceFlinger. This makes subsequent changes in refactoring SurfaceFlinger#composite cleaner and makes tracking the metric more consistent between changes. The refreshStartTime field can be removed entirely from LayerFE's CompositionResult and removed as an argument in LayerFE#onPreComposition. Bug: b/294936197, b/329275965 Test: presubmit Change-Id: I97988315f8982d05aec4b2684d4f5f1826a0fefa --- .../CompositionRefreshArgs.h | 4 ++++ .../include/compositionengine/LayerFE.h | 3 +-- .../include/compositionengine/mock/LayerFE.h | 2 +- .../src/CompositionEngine.cpp | 4 ++-- .../tests/CompositionEngineTest.cpp | 22 +++++++++---------- services/surfaceflinger/LayerFE.cpp | 3 +-- services/surfaceflinger/LayerFE.h | 5 +---- services/surfaceflinger/SurfaceFlinger.cpp | 6 ++++- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index 18a96f4de7..843b5c5c82 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -19,6 +19,7 @@ #include #include #include +#include "utils/Timers.h" #include #include @@ -105,6 +106,9 @@ struct CompositionRefreshArgs { bool hasTrustedPresentationListener = false; ICEPowerCallback* powerCallback = nullptr; + + // System time for when frame refresh starts. Used for stats. + nsecs_t refreshStartTime = 0; }; } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index a1d61323a8..a499928dd0 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -58,8 +58,7 @@ public: // Called before composition starts. Should return true if this layer has // pending updates which would require an extra display refresh cycle to // process. - virtual bool onPreComposition(nsecs_t refreshStartTime, - bool updatingOutputGeometryThisFrame) = 0; + virtual bool onPreComposition(bool updatingOutputGeometryThisFrame) = 0; struct ClientCompositionTargetSettings { enum class BlurSetting { diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index 15e4577ae0..1b8cc2758f 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -43,7 +43,7 @@ public: MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*()); - MOCK_METHOD2(onPreComposition, bool(nsecs_t, bool)); + MOCK_METHOD1(onPreComposition, bool(bool)); MOCK_CONST_METHOD1(prepareClientComposition, std::optional( diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index d87eae3812..b4702085b6 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -181,10 +181,10 @@ void CompositionEngine::preComposition(CompositionRefreshArgs& args) { bool needsAnotherUpdate = false; - mRefreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC); + mRefreshStartTime = args.refreshStartTime; for (auto& layer : args.layers) { - if (layer->onPreComposition(mRefreshStartTime, args.updatingOutputGeometryThisFrame)) { + if (layer->onPreComposition(args.updatingOutputGeometryThisFrame)) { needsAnotherUpdate = true; } } diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index da578e2046..042010edd5 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -214,6 +214,7 @@ struct CompositionTestPreComposition : public CompositionEngineTest { TEST_F(CompositionTestPreComposition, preCompositionSetsFrameTimestamp) { const nsecs_t before = systemTime(SYSTEM_TIME_MONOTONIC); + mRefreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC); mEngine.preComposition(mRefreshArgs); const nsecs_t after = systemTime(SYSTEM_TIME_MONOTONIC); @@ -226,12 +227,9 @@ TEST_F(CompositionTestPreComposition, preCompositionInvokesLayerPreCompositionWi nsecs_t ts1 = 0; nsecs_t ts2 = 0; nsecs_t ts3 = 0; - EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)) - .WillOnce(DoAll(SaveArg<0>(&ts1), Return(false))); - EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)) - .WillOnce(DoAll(SaveArg<0>(&ts2), Return(false))); - EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)) - .WillOnce(DoAll(SaveArg<0>(&ts3), Return(false))); + EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts1), Return(false))); + EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts2), Return(false))); + EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts3), Return(false))); mRefreshArgs.outputs = {mOutput1}; mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE}; @@ -245,9 +243,9 @@ TEST_F(CompositionTestPreComposition, preCompositionInvokesLayerPreCompositionWi } TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) { - EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(false)); - EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false)); - EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false)); mEngine.setNeedsAnotherUpdateForTest(true); @@ -262,9 +260,9 @@ TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) { TEST_F(CompositionTestPreComposition, preCompositionSetsNeedsAnotherUpdateIfAtLeastOneLayerRequestsIt) { - EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(true)); - EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false)); - EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(true)); + EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false)); + EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false)); mRefreshArgs.outputs = {mOutput1}; mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE}; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index 2dbcb841ac..43a4397899 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -82,8 +82,7 @@ const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() return mSnapshot.get(); } -bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) { - mCompositionResult.refreshStartTime = refreshStartTime; +bool LayerFE::onPreComposition(bool) { return mSnapshot->hasReadyFrame; } diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index d584fb7eab..66cb88b20e 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -26,9 +26,6 @@ namespace android { struct CompositionResult { - // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition - // and remove this field. - nsecs_t refreshStartTime = 0; std::vector, ui::LayerStack>> releaseFences; sp lastClientCompositionFence = nullptr; }; @@ -39,7 +36,7 @@ public: // compositionengine::LayerFE overrides const compositionengine::LayerFECompositionState* getCompositionState() const override; - bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override; + bool onPreComposition(bool updatingOutputGeometryThisFrame) override; void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack) override; const char* getDebugName() const override; int32_t getSequence() const override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 78e14aee86..bf210afe6d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2783,12 +2783,16 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( } } + refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC); + for (auto [layer, layerFE] : layers) { + layer->onPreComposition(refreshArgs.refreshStartTime); + } + mCompositionEngine->present(refreshArgs); moveSnapshotsFromCompositionArgs(refreshArgs, layers); for (auto [layer, layerFE] : layers) { CompositionResult compositionResult{layerFE->stealCompositionResult()}; - layer->onPreComposition(compositionResult.refreshStartTime); for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) { Layer* clonedFrom = layer->getClonedFrom().get(); auto owningLayer = clonedFrom ? clonedFrom : layer; -- GitLab From 81deab754e4728f98e09196528e0c3777ca70ed8 Mon Sep 17 00:00:00 2001 From: Zim Date: Tue, 12 Mar 2024 12:37:53 +0000 Subject: [PATCH 062/465] Fix libtracing_perfetto performance impact 1. Avoided checking isPerfettoSdkTracingEnabled on each tracing call. We already checked that before registering perfetto and if the flag was false, toPerfettoCategory will never return a valid category hence we'll fallback to atrace. 2. Added an isPerfettoRegistered check that encapsulates the sdk check for use in getEnabledCategories. This allows us skip the sdk check there We'll need to fix result of getEnabledCategories to only return enabled categories and not just registered ones, but since the flag is off this it's fine for now and can address in a separate cl. Test: atest libtracing_perfetto_tests Bug: 328942318 Bug: 303199244 Change-Id: Ic8fdebd20aba4ac75566c1a1590667891745b92a --- libs/tracing_perfetto/tracing_perfetto.cpp | 3 ++- .../tracing_perfetto/tracing_perfetto_internal.cpp | 14 +++++++------- libs/tracing_perfetto/tracing_perfetto_internal.h | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index c7fb8bd9a8..19d1eb639e 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -131,7 +131,8 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } uint64_t getEnabledCategories() { - if (internal::isPerfettoSdkTracingEnabled()) { + if (internal::isPerfettoRegistered()) { + // TODO(b/303199244): Return only enabled categories and not all registered ones return internal::getDefaultCategories(); } else { return atrace_get_enabled_tags(); diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp index 58ba428610..976db7e430 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp +++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp @@ -70,6 +70,8 @@ PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES); PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES); +std::atomic_bool is_perfetto_registered = false; + struct PerfettoTeCategory* toCategory(uint64_t inCategory) { switch (inCategory) { case TRACE_CATEGORY_ALWAYS: @@ -135,15 +137,11 @@ struct PerfettoTeCategory* toCategory(uint64_t inCategory) { } // namespace -bool isPerfettoSdkTracingEnabled() { - return android::os::perfetto_sdk_tracing(); +bool isPerfettoRegistered() { + return is_perfetto_registered; } struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { - if (!isPerfettoSdkTracingEnabled()) { - return nullptr; - } - struct PerfettoTeCategory* perfettoCategory = toCategory(category); bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED)); @@ -151,9 +149,10 @@ struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { } void registerWithPerfetto(bool test) { - if (!isPerfettoSdkTracingEnabled()) { + if (!android::os::perfetto_sdk_tracing()) { return; } + static std::once_flag registration; std::call_once(registration, [test]() { struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); @@ -161,6 +160,7 @@ void registerWithPerfetto(bool test) { PerfettoProducerInit(args); PerfettoTeInit(); PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES); + is_perfetto_registered = true; }); } diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h index 9a579f162a..79e4b8f1b4 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.h +++ b/libs/tracing_perfetto/tracing_perfetto_internal.h @@ -26,7 +26,7 @@ namespace tracing_perfetto { namespace internal { -bool isPerfettoSdkTracingEnabled(); +bool isPerfettoRegistered(); struct PerfettoTeCategory* toPerfettoCategory(uint64_t category); -- GitLab From 25040239f17b406e33c714f56bf082d76005d20e Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Thu, 22 Feb 2024 17:21:22 +0900 Subject: [PATCH 063/465] Use input token in PointerCaptureRequest Instead of sending boolean to indicate the capture state, InputDispatcher now sends an input window token that requests a pointer capture, or nullptr otherwise. This is useful for some InputReader implementations. Also, this token can be used to verify if a pointer capture changed event from a reader is valid. Bug: 259346762 Bug: 301628662 Test: inputflinger_tests Test: android.view.cts.PointerCaptureTest WindowFocusTests#testPointerCapture Change-Id: Ie8343db6744dc2080f7f1dcff5a630be5c87fa3e --- include/input/Input.h | 14 +++-- .../inputflinger/PointerChoreographer.cpp | 2 +- services/inputflinger/dispatcher/Entry.cpp | 2 +- .../dispatcher/InputDispatcher.cpp | 39 ++++++------ .../inputflinger/dispatcher/InputDispatcher.h | 3 +- .../reader/mapper/CursorInputMapper.cpp | 2 +- .../reader/mapper/TouchInputMapper.cpp | 6 +- .../reader/mapper/TouchpadInputMapper.cpp | 4 +- .../tests/CursorInputMapper_test.cpp | 2 +- .../tests/FakeInputReaderPolicy.cpp | 4 +- .../tests/FakeInputReaderPolicy.h | 2 +- .../tests/InputDispatcher_test.cpp | 63 +++++++++++++------ .../inputflinger/tests/InputReader_test.cpp | 18 +++--- .../tests/PointerChoreographer_test.cpp | 12 ++-- .../tests/fuzzers/TouchpadInputFuzzer.cpp | 5 +- 15 files changed, 107 insertions(+), 71 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index ddc376809e..19f4ab38e8 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -1192,15 +1192,17 @@ public: */ struct PointerCaptureRequest { public: - inline PointerCaptureRequest() : enable(false), seq(0) {} - inline PointerCaptureRequest(bool enable, uint32_t seq) : enable(enable), seq(seq) {} + inline PointerCaptureRequest() : window(), seq(0) {} + inline PointerCaptureRequest(sp window, uint32_t seq) : window(window), seq(seq) {} inline bool operator==(const PointerCaptureRequest& other) const { - return enable == other.enable && seq == other.seq; + return window == other.window && seq == other.seq; } - explicit inline operator bool() const { return enable; } + inline bool isEnable() const { return window != nullptr; } - // True iff this is a request to enable Pointer Capture. - bool enable; + // The requesting window. + // If the request is to enable the capture, this is the input token of the window that requested + // pointer capture. Otherwise, this is nullptr. + sp window; // The sequence number for the request. uint32_t seq; diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 9db3574389..916f8c6b5f 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -277,7 +277,7 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) void PointerChoreographer::notifyPointerCaptureChanged( const NotifyPointerCaptureChangedArgs& args) { - if (args.request.enable) { + if (args.request.isEnable()) { std::scoped_lock _l(mLock); for (const auto& [_, mousePointerController] : mMousePointersByDisplay) { mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index 264dc03e70..0246d606ac 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -110,7 +110,7 @@ PointerCaptureChangedEntry::PointerCaptureChangedEntry(int32_t id, nsecs_t event std::string PointerCaptureChangedEntry::getDescription() const { return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)", - pointerCaptureRequest.enable ? "true" : "false"); + pointerCaptureRequest.isEnable() ? "true" : "false"); } // --- DragEntry --- diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 16875dfebd..34bb746c6c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1715,7 +1715,7 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr; sp token; - if (entry->pointerCaptureRequest.enable) { + if (entry->pointerCaptureRequest.isEnable()) { // Enable Pointer Capture. if (haveWindowWithPointerCapture && (entry->pointerCaptureRequest == mCurrentPointerCaptureRequest)) { @@ -1724,7 +1724,7 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( ALOGI("Skipping dispatch of Pointer Capture being enabled: no state change."); return; } - if (!mCurrentPointerCaptureRequest.enable) { + if (!mCurrentPointerCaptureRequest.isEnable()) { // This can happen if a window requests capture and immediately releases capture. ALOGW("No window requested Pointer Capture."); dropReason = DropReason::NO_POINTER_CAPTURE; @@ -1737,6 +1737,8 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( token = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId); LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture."); + LOG_ALWAYS_FATAL_IF(token != entry->pointerCaptureRequest.window, + "Unexpected requested window for Pointer Capture."); mWindowTokenWithPointerCapture = token; } else { // Disable Pointer Capture. @@ -1756,8 +1758,8 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( } token = mWindowTokenWithPointerCapture; mWindowTokenWithPointerCapture = nullptr; - if (mCurrentPointerCaptureRequest.enable) { - setPointerCaptureLocked(false); + if (mCurrentPointerCaptureRequest.isEnable()) { + setPointerCaptureLocked(nullptr); } } @@ -1765,8 +1767,8 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked( if (connection == nullptr) { // Window has gone away, clean up Pointer Capture state. mWindowTokenWithPointerCapture = nullptr; - if (mCurrentPointerCaptureRequest.enable) { - setPointerCaptureLocked(false); + if (mCurrentPointerCaptureRequest.isEnable()) { + setPointerCaptureLocked(nullptr); } return; } @@ -3832,9 +3834,10 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, case EventEntry::Type::POINTER_CAPTURE_CHANGED: { const auto& captureEntry = static_cast(eventEntry); - status = connection->inputPublisher - .publishCaptureEvent(dispatchEntry->seq, captureEntry.id, - captureEntry.pointerCaptureRequest.enable); + status = + connection->inputPublisher + .publishCaptureEvent(dispatchEntry->seq, captureEntry.id, + captureEntry.pointerCaptureRequest.isEnable()); break; } @@ -4713,7 +4716,7 @@ void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) { void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) { if (debugInboundEventDetails()) { ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime, - args.request.enable ? "true" : "false"); + args.request.isEnable() ? "true" : "false"); } bool needWake = false; @@ -5784,7 +5787,7 @@ std::string InputDispatcher::dumpPointerCaptureStateLocked() const { std::string dump; dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n", - toString(mCurrentPointerCaptureRequest.enable)); + toString(mCurrentPointerCaptureRequest.isEnable())); std::string windowName = "None"; if (mWindowTokenWithPointerCapture) { @@ -6203,7 +6206,7 @@ void InputDispatcher::requestPointerCapture(const sp& windowToken, bool return; } - if (enabled == mCurrentPointerCaptureRequest.enable) { + if (enabled == mCurrentPointerCaptureRequest.isEnable()) { ALOGW("Ignoring request to %s Pointer Capture: " "window has %s requested pointer capture.", enabled ? "enable" : "disable", enabled ? "already" : "not"); @@ -6219,7 +6222,7 @@ void InputDispatcher::requestPointerCapture(const sp& windowToken, bool } } - setPointerCaptureLocked(enabled); + setPointerCaptureLocked(enabled ? windowToken : nullptr); } // release lock // Wake the thread to process command entries. @@ -6849,14 +6852,14 @@ void InputDispatcher::onFocusChangedLocked( } void InputDispatcher::disablePointerCaptureForcedLocked() { - if (!mCurrentPointerCaptureRequest.enable && !mWindowTokenWithPointerCapture) { + if (!mCurrentPointerCaptureRequest.isEnable() && !mWindowTokenWithPointerCapture) { return; } ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus."); - if (mCurrentPointerCaptureRequest.enable) { - setPointerCaptureLocked(false); + if (mCurrentPointerCaptureRequest.isEnable()) { + setPointerCaptureLocked(nullptr); } if (!mWindowTokenWithPointerCapture) { @@ -6876,8 +6879,8 @@ void InputDispatcher::disablePointerCaptureForcedLocked() { mInboundQueue.push_front(std::move(entry)); } -void InputDispatcher::setPointerCaptureLocked(bool enable) { - mCurrentPointerCaptureRequest.enable = enable; +void InputDispatcher::setPointerCaptureLocked(const sp& windowToken) { + mCurrentPointerCaptureRequest.window = windowToken; mCurrentPointerCaptureRequest.seq++; auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) { scoped_unlock unlock(mLock); diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index d6eba64dd0..13571b3b5f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -421,7 +421,8 @@ private: void disablePointerCaptureForcedLocked() REQUIRES(mLock); // Set the Pointer Capture state in the Policy. - void setPointerCaptureLocked(bool enable) REQUIRES(mLock); + // The window is not nullptr for requests to enable, otherwise it is nullptr. + void setPointerCaptureLocked(const sp& window) REQUIRES(mLock); // Dispatcher state at time of last ANR. std::string mLastAnrState GUARDED_BY(mLock); diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 06f10e5445..c8cc5dcc83 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -486,7 +486,7 @@ void CursorInputMapper::configureBasicParams() { } void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) { - if (config.pointerCaptureRequest.enable) { + if (config.pointerCaptureRequest.isEnable()) { if (mParameters.mode == Parameters::Mode::POINTER) { mParameters.mode = Parameters::Mode::POINTER_RELATIVE; mSource = AINPUT_SOURCE_MOUSE_RELATIVE; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 3c26d1d73e..7d27d4a9ce 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -923,7 +923,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) // Determine device mode. if (mParameters.deviceType == Parameters::DeviceType::POINTER && - mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.enable) { + mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.isEnable()) { mSource = AINPUT_SOURCE_MOUSE; mDeviceMode = DeviceMode::POINTER; if (hasStylus()) { @@ -1038,7 +1038,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) (mDeviceMode == DeviceMode::POINTER) || // - when pointer capture is enabled, to preserve the mouse cursor position; (mParameters.deviceType == Parameters::DeviceType::POINTER && - mConfig.pointerCaptureRequest.enable) || + mConfig.pointerCaptureRequest.isEnable()) || // - when we should be showing touches; (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) || // - when we should be showing a pointer icon for direct styluses. @@ -1047,7 +1047,7 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if (mPointerController == nullptr) { mPointerController = getContext()->getPointerController(getDeviceId()); } - if (mConfig.pointerCaptureRequest.enable) { + if (mConfig.pointerCaptureRequest.isEnable()) { mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); } } else { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index eacc66eeab..99f9e24169 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -410,9 +410,9 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, .setBoolValues({config.touchpadRightClickZoneEnabled}); } std::list out; - if ((!changes.any() && config.pointerCaptureRequest.enable) || + if ((!changes.any() && config.pointerCaptureRequest.isEnable()) || changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) { - mPointerCaptured = config.pointerCaptureRequest.enable; + mPointerCaptured = config.pointerCaptureRequest.isEnable(); // The motion ranges are going to change, so bump the generation to clear the cached ones. bumpGeneration(); if (mPointerCaptured) { diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index de740672fc..c44f88006e 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -166,7 +166,7 @@ protected: } void setPointerCapture(bool enabled) { - mReaderConfiguration.pointerCaptureRequest.enable = enabled; + mReaderConfiguration.pointerCaptureRequest.window = enabled ? sp::make() : nullptr; mReaderConfiguration.pointerCaptureRequest.seq = 1; int32_t generation = mDevice->getGeneration(); std::list args = diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 9e9371248e..8f593b553a 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -178,8 +178,8 @@ void FakeInputReaderPolicy::setTouchAffineTransformation(const TouchAffineTransf transform = t; } -PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) { - mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++}; +PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp& window) { + mConfig.pointerCaptureRequest = {window, mNextPointerCaptureSequenceNumber++}; return mConfig.pointerCaptureRequest; } diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index da5085db7c..710bb5496f 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -68,7 +68,7 @@ public: TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation); void setTouchAffineTransformation(const TouchAffineTransformation t); - PointerCaptureRequest setPointerCapture(bool enabled); + PointerCaptureRequest setPointerCapture(const sp& window); void setShowTouches(bool enabled); void setDefaultPointerDisplayId(int32_t pointerDisplayId); void setPointerGestureEnabled(bool enabled); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 60d598ff80..55d5f1303f 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -308,17 +308,22 @@ public: "signal"; } - PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) { + PointerCaptureRequest assertSetPointerCaptureCalled(const sp& window, + bool enabled) { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); - if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms, - [this, enabled]() REQUIRES(mLock) { - return mPointerCaptureRequest->enable == - enabled; - })) { - ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << enabled - << ") to be called."; + if (!mPointerCaptureChangedCondition + .wait_for(lock, 100ms, [this, enabled, window]() REQUIRES(mLock) { + if (enabled) { + return mPointerCaptureRequest->isEnable() && + mPointerCaptureRequest->window == window->getToken(); + } else { + return !mPointerCaptureRequest->isEnable(); + } + })) { + ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << window->getName() << ", " + << enabled << ") to be called."; return {}; } auto request = *mPointerCaptureRequest; @@ -333,7 +338,7 @@ public: if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) { FAIL() << "Expected setPointerCapture(request) to not be called, but was called. " "enabled = " - << std::to_string(mPointerCaptureRequest->enable); + << std::to_string(mPointerCaptureRequest->isEnable()); } mPointerCaptureRequest.reset(); } @@ -9888,7 +9893,7 @@ protected: PointerCaptureRequest requestAndVerifyPointerCapture(const sp& window, bool enabled) { mDispatcher->requestPointerCapture(window->getToken(), enabled); - auto request = mFakePolicy->assertSetPointerCaptureCalled(enabled); + auto request = mFakePolicy->assertSetPointerCaptureCalled(window, enabled); notifyPointerCaptureChanged(request); window->consumeCaptureEvent(enabled); return request; @@ -9921,7 +9926,7 @@ TEST_F(InputDispatcherPointerCaptureTests, DisablesPointerCaptureAfterWindowLose mWindow->consumeCaptureEvent(false); mWindow->consumeFocusEvent(false); mSecondWindow->consumeFocusEvent(true); - mFakePolicy->assertSetPointerCaptureCalled(false); + mFakePolicy->assertSetPointerCaptureCalled(mWindow, false); // Ensure that additional state changes from InputReader are not sent to the window. notifyPointerCaptureChanged({}); @@ -9940,7 +9945,7 @@ TEST_F(InputDispatcherPointerCaptureTests, UnexpectedStateChangeDisablesPointerC notifyPointerCaptureChanged(request); // Ensure that Pointer Capture is disabled. - mFakePolicy->assertSetPointerCaptureCalled(false); + mFakePolicy->assertSetPointerCaptureCalled(mWindow, false); mWindow->consumeCaptureEvent(false); mWindow->assertNoEvents(); } @@ -9950,13 +9955,13 @@ TEST_F(InputDispatcherPointerCaptureTests, OutOfOrderRequests) { // The first window loses focus. setFocusedWindow(mSecondWindow); - mFakePolicy->assertSetPointerCaptureCalled(false); + mFakePolicy->assertSetPointerCaptureCalled(mWindow, false); mWindow->consumeCaptureEvent(false); // Request Pointer Capture from the second window before the notification from InputReader // arrives. mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true); - auto request = mFakePolicy->assertSetPointerCaptureCalled(true); + auto request = mFakePolicy->assertSetPointerCaptureCalled(mSecondWindow, true); // InputReader notifies Pointer Capture was disabled (because of the focus change). notifyPointerCaptureChanged({}); @@ -9971,11 +9976,11 @@ TEST_F(InputDispatcherPointerCaptureTests, OutOfOrderRequests) { TEST_F(InputDispatcherPointerCaptureTests, EnableRequestFollowsSequenceNumbers) { // App repeatedly enables and disables capture. mDispatcher->requestPointerCapture(mWindow->getToken(), true); - auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(true); + auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true); mDispatcher->requestPointerCapture(mWindow->getToken(), false); - mFakePolicy->assertSetPointerCaptureCalled(false); + mFakePolicy->assertSetPointerCaptureCalled(mWindow, false); mDispatcher->requestPointerCapture(mWindow->getToken(), true); - auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(true); + auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true); // InputReader notifies that PointerCapture has been enabled for the first request. Since the // first request is now stale, this should do nothing. @@ -9992,10 +9997,10 @@ TEST_F(InputDispatcherPointerCaptureTests, RapidToggleRequests) { // App toggles pointer capture off and on. mDispatcher->requestPointerCapture(mWindow->getToken(), false); - mFakePolicy->assertSetPointerCaptureCalled(false); + mFakePolicy->assertSetPointerCaptureCalled(mWindow, false); mDispatcher->requestPointerCapture(mWindow->getToken(), true); - auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(true); + auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true); // InputReader notifies that the latest "enable" request was processed, while skipping over the // preceding "disable" request. @@ -10047,6 +10052,26 @@ TEST_F(InputDispatcherPointerCaptureTests, MouseHoverAndPointerCapture) { mWindow->assertNoEvents(); } +using InputDispatcherPointerCaptureDeathTest = InputDispatcherPointerCaptureTests; + +TEST_F(InputDispatcherPointerCaptureDeathTest, + NotifyPointerCaptureChangedWithWrongTokenAbortsDispatcher) { + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + ScopedSilentDeath _silentDeath; + + mDispatcher->requestPointerCapture(mWindow->getToken(), true); + auto request = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true); + + // Dispatch a pointer changed event with a wrong token. + request.window = mSecondWindow->getToken(); + ASSERT_DEATH( + { + notifyPointerCaptureChanged(request); + mSecondWindow->consumeCaptureEvent(true); + }, + "Unexpected requested window for Pointer Capture."); +} + class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest { protected: constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 835f8b89c3..1d46c9a1e2 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -1165,18 +1165,18 @@ TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) { TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) { NotifyPointerCaptureChangedArgs args; - auto request = mFakePolicy->setPointerCapture(true); + auto request = mFakePolicy->setPointerCapture(/*window=*/sp::make()); mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); mReader->loopOnce(); mFakeListener->assertNotifyCaptureWasCalled(&args); - ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled."; + ASSERT_TRUE(args.request.isEnable()) << "Pointer Capture should be enabled."; ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match."; - mFakePolicy->setPointerCapture(false); + mFakePolicy->setPointerCapture(/*window=*/nullptr); mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE); mReader->loopOnce(); mFakeListener->assertNotifyCaptureWasCalled(&args); - ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled."; + ASSERT_FALSE(args.request.isEnable()) << "Pointer Capture should be disabled."; // Verify that the Pointer Capture state is not updated when the configuration value // does not change. @@ -9802,7 +9802,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerCapture(true); + mFakePolicy->setPointerCapture(/*window=*/sp::make()); mFakePolicy->setPointerController(fakePointerController); MultiTouchInputMapper& mapper = constructAndAddMapper(); @@ -9934,7 +9934,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) { ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action); // non captured touchpad should be a mouse source - mFakePolicy->setPointerCapture(false); + mFakePolicy->setPointerCapture(/*window=*/nullptr); configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs)); ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); @@ -10012,14 +10012,14 @@ TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) { prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakePolicy->setPointerController(fakePointerController); - mFakePolicy->setPointerCapture(false); + mFakePolicy->setPointerCapture(/*window=*/nullptr); MultiTouchInputMapper& mapper = constructAndAddMapper(); // uncaptured touchpad should be a pointer device ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); // captured touchpad should be a touchpad device - mFakePolicy->setPointerCapture(true); + mFakePolicy->setPointerCapture(/*window=*/sp::make()); configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE); ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources()); } @@ -10090,7 +10090,7 @@ protected: prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution); // In order to enable swipe and freeform gesture in pointer mode, pointer capture // needs to be disabled, and the pointer gesture needs to be enabled. - mFakePolicy->setPointerCapture(false); + mFakePolicy->setPointerCapture(/*window=*/nullptr); mFakePolicy->setPointerGestureEnabled(true); mFakePolicy->setPointerController(fakePointerController); diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index e9e50619ee..cbfb28faea 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -413,7 +413,8 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}}); mChoreographer.notifyPointerCaptureChanged( NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC), - PointerCaptureRequest(/*enable=*/true, /*seq=*/0))); + PointerCaptureRequest(/*window=*/sp::make(), + /*seq=*/0))); // Notify motion as if pointer capture is enabled. mChoreographer.notifyMotion( @@ -450,7 +451,8 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) { // Enable pointer capture and check if the PointerController hid the pointer. mChoreographer.notifyPointerCaptureChanged( NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), - PointerCaptureRequest(/*enable=*/true, /*seq=*/0))); + PointerCaptureRequest(/*window=*/sp::make(), + /*seq=*/0))); ASSERT_FALSE(pc->isPointerShown()); } @@ -1295,7 +1297,8 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { // Assume that pointer capture is enabled. mChoreographer.notifyPointerCaptureChanged( NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), - PointerCaptureRequest(/*enable=*/true, /*seq=*/0))); + PointerCaptureRequest(/*window=*/sp::make(), + /*seq=*/0))); // Notify motion as if pointer capture is enabled. mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD) @@ -1329,7 +1332,8 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledTouchpadHidesPointer) // Enable pointer capture and check if the PointerController hid the pointer. mChoreographer.notifyPointerCaptureChanged( NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC), - PointerCaptureRequest(/*enable=*/true, /*seq=*/0))); + PointerCaptureRequest(/*window=*/sp::make(), + /*seq=*/0))); ASSERT_FALSE(pc->isPointerShown()); } diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp index c2bf275611..643e8b9f97 100644 --- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp @@ -125,6 +125,9 @@ void setTouchpadSettings(ThreadSafeFuzzedDataProvider& fdp, InputReaderConfigura config.touchpadTapToClickEnabled = fdp.ConsumeBool(); config.touchpadTapDraggingEnabled = fdp.ConsumeBool(); config.touchpadRightClickZoneEnabled = fdp.ConsumeBool(); + + config.pointerCaptureRequest.window = fdp.ConsumeBool() ? sp::make() : nullptr; + config.pointerCaptureRequest.seq = fdp.ConsumeIntegral(); } } // namespace @@ -145,7 +148,6 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { // Some settings are fuzzed here, as well as in the main loop, to provide randomized data to the // TouchpadInputMapper constructor. setTouchpadSettings(*fdp, policyConfig); - policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool(); TouchpadInputMapper& mapper = getMapperForDevice(*fdp, device, policyConfig); @@ -164,7 +166,6 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { setTouchpadSettings(*fdp, policyConfig); - policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool(); std::list unused = mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, InputReaderConfiguration::Change( -- GitLab From e0a4cfe1f3d63c3ccb8e785ba21f8486fb5aa779 Mon Sep 17 00:00:00 2001 From: Nergi Rahardi Date: Mon, 11 Mar 2024 13:18:59 +0900 Subject: [PATCH 064/465] Adds support for absolute-coordinates from mouse bridge This adds support for systems that could only sends absolute coordinates as move event. Bug: 329006981 Test: atest inputflinger_tests Change-Id: I3e1418570f6aa548765a160d36883a3e8e0c9b8a --- .../inputflinger/PointerChoreographer.cpp | 33 ++++++++++++------- .../inputflinger/include/NotifyArgsBuilders.h | 4 ++- .../tests/PointerChoreographer_test.cpp | 32 ++++++++++++++++++ 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 9db3574389..b8d0c41f75 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -122,21 +122,32 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio } auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); + NotifyMotionArgs newArgs(args); + newArgs.displayId = displayId; - const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); - const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); - pc.move(deltaX, deltaY); + if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) { + // This is an absolute mouse device that knows about the location of the cursor on the + // display, so set the cursor position to the specified location. + const auto [x, y] = pc.getPosition(); + const float deltaX = args.xCursorPosition - x; + const float deltaY = args.yCursorPosition - y; + newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); + newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); + pc.setPosition(args.xCursorPosition, args.yCursorPosition); + } else { + // This is a relative mouse, so move the cursor by the specified amount. + const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); + const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); + pc.move(deltaX, deltaY); + const auto [x, y] = pc.getPosition(); + newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + newArgs.xCursorPosition = x; + newArgs.yCursorPosition = y; + } if (canUnfadeOnDisplay(displayId)) { pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); } - - const auto [x, y] = pc.getPosition(); - NotifyMotionArgs newArgs(args); - newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); - newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); - newArgs.xCursorPosition = x; - newArgs.yCursorPosition = y; - newArgs.displayId = displayId; return newArgs; } diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h index e4363a416c..8ffbc11a13 100644 --- a/services/inputflinger/include/NotifyArgsBuilders.h +++ b/services/inputflinger/include/NotifyArgsBuilders.h @@ -107,7 +107,9 @@ public: // Set mouse cursor position for the most common cases to avoid boilerplate. if (mSource == AINPUT_SOURCE_MOUSE && - !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) { + !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && + BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) && + BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) { mRawXCursorPosition = pointerCoords[0].getX(); mRawYCursorPosition = pointerCoords[0].getY(); } diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index e9e50619ee..0b43a3335d 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -355,6 +355,38 @@ TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) { AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220))); } +TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); + + // Set initial position of the PointerController. + pc->setPosition(100, 200); + const auto absoluteMousePointer = PointerBuilder(/*id=*/0, ToolType::MOUSE) + .axis(AMOTION_EVENT_AXIS_X, 110) + .axis(AMOTION_EVENT_AXIS_Y, 220); + + // Make NotifyMotionArgs and notify Choreographer. + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) + .pointer(absoluteMousePointer) + .deviceId(DEVICE_ID) + .displayId(ADISPLAY_ID_NONE) + .build()); + + // Check that the PointerController updated the position and the pointer is shown. + pc->assertPosition(110, 220); + ASSERT_TRUE(pc->isPointerShown()); + + // Check that x-y coordinates, displayId and cursor position are correctly updated. + mTestListener.assertNotifyMotionWasCalled( + AllOf(WithCoords(110, 220), WithRelativeMotion(10, 20), WithDisplayId(DISPLAY_ID), + WithCursorPosition(110, 220))); +} + TEST_F(PointerChoreographerTest, AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) { // Add two displays and set one to default. -- GitLab From a6f572f92437f5ace0b99df16af1f49cad8b260b Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 29 Feb 2024 16:28:21 +0000 Subject: [PATCH 065/465] Add native InputTransferToken Added a native class that corresponds to the Java InputTransferToken. Test: SurfaceControlInputReceiverTests Test: AInputTransferTokenTest Bug: 324271765 Change-Id: Ida2a7b34338560dfed9af7f510d64372e41384af --- include/android/input_transfer_token_jni.h | 68 ++++++++++++++++++++++ libs/gui/include/gui/InputTransferToken.h | 53 +++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 include/android/input_transfer_token_jni.h create mode 100644 libs/gui/include/gui/InputTransferToken.h diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h new file mode 100644 index 0000000000..ba5f6f2bdd --- /dev/null +++ b/include/android/input_transfer_token_jni.h @@ -0,0 +1,68 @@ +/* + * Copyright 2024 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. + */ +/** + * @addtogroup NativeActivity Native Activity + * @{ + */ +/** + * @file input_transfer_token_jni.h + */ +#ifndef ANDROID_INPUT_TRANSFER_TOKEN_JNI_H +#define ANDROID_INPUT_TRANSFER_TOKEN_JNI_H +#include +#include + +__BEGIN_DECLS +struct AInputTransferToken; + +/** + * AInputTransferToken can be used to request focus on or to transfer touch gesture to and from + * an embedded SurfaceControl + */ +typedef struct AInputTransferToken AInputTransferToken; + +/** + * Return the AInputTransferToken wrapped by a Java InputTransferToken object. This must be released + * using AInputTransferToken_release + * + * inputTransferTokenObj must be a non-null instance of android.window.InputTransferToken. + * + * Available since API level 35. + */ +AInputTransferToken* _Nonnull AInputTransferToken_fromJava(JNIEnv* _Nonnull env, + jobject _Nonnull inputTransferTokenObj) __INTRODUCED_IN(__ANDROID_API_V__); +/** + * Return the Java InputTransferToken object that wraps AInputTransferToken + * + * aInputTransferToken must be non null and the returned value is an object of instance + * android.window.InputTransferToken. + * + * Available since API level 35. + */ +jobject _Nonnull AInputTransferToken_toJava(JNIEnv* _Nonnull env, + const AInputTransferToken* _Nonnull aInputTransferToken) __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Removes a reference that was previously acquired in native. + * + * Available since API level 35. + */ +void AInputTransferToken_release(AInputTransferToken* _Nonnull aInputTransferToken) + __INTRODUCED_IN(__ANDROID_API_V__); + +__END_DECLS +#endif // ANDROID_INPUT_TRANSFER_TOKEN_JNI_H +/** @} */ diff --git a/libs/gui/include/gui/InputTransferToken.h b/libs/gui/include/gui/InputTransferToken.h new file mode 100644 index 0000000000..6530b5069a --- /dev/null +++ b/libs/gui/include/gui/InputTransferToken.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace android { +struct InputTransferToken : public RefBase, Parcelable { +public: + InputTransferToken() { mToken = new BBinder(); } + + InputTransferToken(const sp& token) { mToken = token; } + + status_t writeToParcel(Parcel* parcel) const override { + SAFE_PARCEL(parcel->writeStrongBinder, mToken); + return NO_ERROR; + } + + status_t readFromParcel(const Parcel* parcel) { + SAFE_PARCEL(parcel->readStrongBinder, &mToken); + return NO_ERROR; + }; + + sp mToken; +}; + +static inline bool operator==(const sp& token1, + const sp& token2) { + if (token1.get() == token2.get()) { + return true; + } + return token1->mToken == token2->mToken; +} + +} // namespace android \ No newline at end of file -- GitLab From 6acb3ed0c8b623bc6bb082689370015328ec7ac6 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 13 Mar 2024 14:16:33 +0000 Subject: [PATCH 066/465] touchpad: hide non-error Gestures library logs These logs can be extremely verbose, especially the tap-to-click messages, and they're not particularly useful for triaging bugs. Only enable them when the touchpad gestures debugging property is set. Bug: 314743031 Test: use a touchpad, and check no TTC logs appear. Run `adb shell 'stop && setprop log.tag.TouchpadInputMapperGestures DEBUG && start'`, use a touchpad, and check the TTC logs do appear Change-Id: I4218fc856080199b974ed7a5746a7acf2283cfaa --- .../mapper/gestures/GesturesLogging.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp index 81b4968df8..26028c5643 100644 --- a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp +++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp @@ -26,15 +26,30 @@ extern "C" { +namespace { + +/** + * Log details of each gesture output by the gestures library. + * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires + * restarting the shell) + */ +const bool DEBUG_TOUCHPAD_GESTURES = + __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures", + ANDROID_LOG_INFO); + +} // namespace + void gestures_log(int verb, const char* fmt, ...) { va_list args; va_start(args, fmt); if (verb == GESTURES_LOG_ERROR) { LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, fmt, args); - } else if (verb == GESTURES_LOG_INFO) { - LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args); - } else { - LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args); + } else if (DEBUG_TOUCHPAD_GESTURES) { + if (verb == GESTURES_LOG_INFO) { + LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args); + } else { + LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args); + } } va_end(args); } -- GitLab From f30565a7f6023fd76919dd5f926e6ddbb2f8e8f1 Mon Sep 17 00:00:00 2001 From: Biswarup Pal Date: Wed, 13 Mar 2024 14:09:36 +0000 Subject: [PATCH 067/465] Run libtracing_perfetto_tests in postsubmit Test: atest libtracing_perfetto_tests Bug: 303199244 Change-Id: I4b1357ebf0d2d0dcf2f3e7615bdba0d7a3d42649 --- libs/tracing_perfetto/TEST_MAPPING | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 libs/tracing_perfetto/TEST_MAPPING diff --git a/libs/tracing_perfetto/TEST_MAPPING b/libs/tracing_perfetto/TEST_MAPPING new file mode 100644 index 0000000000..1805e1819d --- /dev/null +++ b/libs/tracing_perfetto/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "libtracing_perfetto_tests" + } + ] +} -- GitLab From 0438ca8a922661b3a870f61ab6fcb5fab4e5ffb8 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 12 Mar 2024 14:27:25 -0700 Subject: [PATCH 068/465] Move InputConsumer into separate files This helps to keep track of things, and it also allows easier deletion in the future. Bug: 311142655 Test: none Change-Id: Iece2bf6857ab05f86072031a4cf3a36f843b1634 --- include/input/InputConsumer.h | 249 +++++ include/input/InputTransport.h | 225 ----- libs/gui/tests/EndToEndNativeInputTest.cpp | 1 + libs/input/Android.bp | 1 + libs/input/InputConsumer.cpp | 939 ++++++++++++++++++ libs/input/InputTransport.cpp | 883 +--------------- .../tests/InputPublisherAndConsumer_test.cpp | 1 + libs/input/tests/TouchResampling_test.cpp | 1 + .../inputflinger/tests/FakeWindowHandle.h | 1 + .../tests/InputDispatcher_test.cpp | 1 + 10 files changed, 1195 insertions(+), 1107 deletions(-) create mode 100644 include/input/InputConsumer.h create mode 100644 libs/input/InputConsumer.cpp diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h new file mode 100644 index 0000000000..560e804c68 --- /dev/null +++ b/include/input/InputConsumer.h @@ -0,0 +1,249 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/* + * Native input transport. + * + * The InputConsumer is used by the application to receive events from the input dispatcher. + */ + +#include "InputTransport.h" + +namespace android { + +/* + * Consumes input events from an input channel. + */ +class InputConsumer { +public: + /* Create a consumer associated with an input channel. */ + explicit InputConsumer(const std::shared_ptr& channel); + /* Create a consumer associated with an input channel, override resampling system property */ + explicit InputConsumer(const std::shared_ptr& channel, + bool enableTouchResampling); + + /* Destroys the consumer and releases its input channel. */ + ~InputConsumer(); + + /* Gets the underlying input channel. */ + inline std::shared_ptr getChannel() { return mChannel; } + + /* Consumes an input event from the input channel and copies its contents into + * an InputEvent object created using the specified factory. + * + * Tries to combine a series of move events into larger batches whenever possible. + * + * If consumeBatches is false, then defers consuming pending batched events if it + * is possible for additional samples to be added to them later. Call hasPendingBatch() + * to determine whether a pending batch is available to be consumed. + * + * If consumeBatches is true, then events are still batched but they are consumed + * immediately as soon as the input channel is exhausted. + * + * The frameTime parameter specifies the time when the current display frame started + * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown. + * + * The returned sequence number is never 0 unless the operation failed. + * + * Returns OK on success. + * Returns WOULD_BLOCK if there is no event present. + * Returns DEAD_OBJECT if the channel's peer has been closed. + * Returns NO_MEMORY if the event could not be created. + * Other errors probably indicate that the channel is broken. + */ + status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime, + uint32_t* outSeq, InputEvent** outEvent); + + /* Sends a finished signal to the publisher to inform it that the message + * with the specified sequence number has finished being process and whether + * the message was handled by the consumer. + * + * Returns OK on success. + * Returns BAD_VALUE if seq is 0. + * Other errors probably indicate that the channel is broken. + */ + status_t sendFinishedSignal(uint32_t seq, bool handled); + + status_t sendTimeline(int32_t inputEventId, + std::array timeline); + + /* Returns true if there is a pending batch. + * + * Should be called after calling consume() with consumeBatches == false to determine + * whether consume() should be called again later on with consumeBatches == true. + */ + bool hasPendingBatch() const; + + /* Returns the source of first pending batch if exist. + * + * Should be called after calling consume() with consumeBatches == false to determine + * whether consume() should be called again later on with consumeBatches == true. + */ + int32_t getPendingBatchSource() const; + + /* Returns true when there is *likely* a pending batch or a pending event in the channel. + * + * This is only a performance hint and may return false negative results. Clients should not + * rely on availability of the message based on the return value. + */ + bool probablyHasInput() const; + + std::string dump() const; + +private: + // True if touch resampling is enabled. + const bool mResampleTouch; + + std::shared_ptr mChannel; + + // The current input message. + InputMessage mMsg; + + // True if mMsg contains a valid input message that was deferred from the previous + // call to consume and that still needs to be handled. + bool mMsgDeferred; + + // Batched motion events per device and source. + struct Batch { + std::vector samples; + }; + std::vector mBatches; + + // Touch state per device and source, only for sources of class pointer. + struct History { + nsecs_t eventTime; + BitSet32 idBits; + int32_t idToIndex[MAX_POINTER_ID + 1]; + PointerCoords pointers[MAX_POINTERS]; + + void initializeFrom(const InputMessage& msg) { + eventTime = msg.body.motion.eventTime; + idBits.clear(); + for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { + uint32_t id = msg.body.motion.pointers[i].properties.id; + idBits.markBit(id); + idToIndex[id] = i; + pointers[i].copyFrom(msg.body.motion.pointers[i].coords); + } + } + + void initializeFrom(const History& other) { + eventTime = other.eventTime; + idBits = other.idBits; // temporary copy + for (size_t i = 0; i < other.idBits.count(); i++) { + uint32_t id = idBits.clearFirstMarkedBit(); + int32_t index = other.idToIndex[id]; + idToIndex[id] = index; + pointers[index].copyFrom(other.pointers[index]); + } + idBits = other.idBits; // final copy + } + + const PointerCoords& getPointerById(uint32_t id) const { return pointers[idToIndex[id]]; } + + bool hasPointerId(uint32_t id) const { return idBits.hasBit(id); } + }; + struct TouchState { + int32_t deviceId; + int32_t source; + size_t historyCurrent; + size_t historySize; + History history[2]; + History lastResample; + + void initialize(int32_t incomingDeviceId, int32_t incomingSource) { + deviceId = incomingDeviceId; + source = incomingSource; + historyCurrent = 0; + historySize = 0; + lastResample.eventTime = 0; + lastResample.idBits.clear(); + } + + void addHistory(const InputMessage& msg) { + historyCurrent ^= 1; + if (historySize < 2) { + historySize += 1; + } + history[historyCurrent].initializeFrom(msg); + } + + const History* getHistory(size_t index) const { + return &history[(historyCurrent + index) & 1]; + } + + bool recentCoordinatesAreIdentical(uint32_t id) const { + // Return true if the two most recently received "raw" coordinates are identical + if (historySize < 2) { + return false; + } + if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) { + return false; + } + float currentX = getHistory(0)->getPointerById(id).getX(); + float currentY = getHistory(0)->getPointerById(id).getY(); + float previousX = getHistory(1)->getPointerById(id).getX(); + float previousY = getHistory(1)->getPointerById(id).getY(); + if (currentX == previousX && currentY == previousY) { + return true; + } + return false; + } + }; + std::vector mTouchStates; + + // Chain of batched sequence numbers. When multiple input messages are combined into + // a batch, we append a record here that associates the last sequence number in the + // batch with the previous one. When the finished signal is sent, we traverse the + // chain to individually finish all input messages that were part of the batch. + struct SeqChain { + uint32_t seq; // sequence number of batched input message + uint32_t chain; // sequence number of previous batched input message + }; + std::vector mSeqChains; + + // The time at which each event with the sequence number 'seq' was consumed. + // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency + // This collection is populated when the event is received, and the entries are erased when the + // events are finished. It should not grow infinitely because if an event is not ack'd, ANR + // will be raised for that connection, and no further events will be posted to that channel. + std::unordered_map mConsumeTimes; + + status_t consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime, uint32_t* outSeq, + InputEvent** outEvent); + status_t consumeSamples(InputEventFactoryInterface* factory, Batch& batch, size_t count, + uint32_t* outSeq, InputEvent** outEvent); + + void updateTouchState(InputMessage& msg); + void resampleTouchState(nsecs_t frameTime, MotionEvent* event, const InputMessage* next); + + ssize_t findBatch(int32_t deviceId, int32_t source) const; + ssize_t findTouchState(int32_t deviceId, int32_t source) const; + + nsecs_t getConsumeTime(uint32_t seq) const; + void popConsumeTime(uint32_t seq); + status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled); + + static void rewriteMessage(TouchState& state, InputMessage& msg); + static bool canAddSample(const Batch& batch, const InputMessage* msg); + static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time); + + static bool isTouchResamplingEnabled(); +}; + +} // namespace android diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index aca4b622d1..5f9c8f5a5c 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -451,229 +451,4 @@ private: InputVerifier mInputVerifier; }; -/* - * Consumes input events from an input channel. - */ -class InputConsumer { -public: - /* Create a consumer associated with an input channel. */ - explicit InputConsumer(const std::shared_ptr& channel); - /* Create a consumer associated with an input channel, override resampling system property */ - explicit InputConsumer(const std::shared_ptr& channel, - bool enableTouchResampling); - - /* Destroys the consumer and releases its input channel. */ - ~InputConsumer(); - - /* Gets the underlying input channel. */ - inline std::shared_ptr getChannel() { return mChannel; } - - /* Consumes an input event from the input channel and copies its contents into - * an InputEvent object created using the specified factory. - * - * Tries to combine a series of move events into larger batches whenever possible. - * - * If consumeBatches is false, then defers consuming pending batched events if it - * is possible for additional samples to be added to them later. Call hasPendingBatch() - * to determine whether a pending batch is available to be consumed. - * - * If consumeBatches is true, then events are still batched but they are consumed - * immediately as soon as the input channel is exhausted. - * - * The frameTime parameter specifies the time when the current display frame started - * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown. - * - * The returned sequence number is never 0 unless the operation failed. - * - * Returns OK on success. - * Returns WOULD_BLOCK if there is no event present. - * Returns DEAD_OBJECT if the channel's peer has been closed. - * Returns NO_MEMORY if the event could not be created. - * Other errors probably indicate that the channel is broken. - */ - status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime, - uint32_t* outSeq, InputEvent** outEvent); - - /* Sends a finished signal to the publisher to inform it that the message - * with the specified sequence number has finished being process and whether - * the message was handled by the consumer. - * - * Returns OK on success. - * Returns BAD_VALUE if seq is 0. - * Other errors probably indicate that the channel is broken. - */ - status_t sendFinishedSignal(uint32_t seq, bool handled); - - status_t sendTimeline(int32_t inputEventId, - std::array timeline); - - /* Returns true if there is a pending batch. - * - * Should be called after calling consume() with consumeBatches == false to determine - * whether consume() should be called again later on with consumeBatches == true. - */ - bool hasPendingBatch() const; - - /* Returns the source of first pending batch if exist. - * - * Should be called after calling consume() with consumeBatches == false to determine - * whether consume() should be called again later on with consumeBatches == true. - */ - int32_t getPendingBatchSource() const; - - /* Returns true when there is *likely* a pending batch or a pending event in the channel. - * - * This is only a performance hint and may return false negative results. Clients should not - * rely on availability of the message based on the return value. - */ - bool probablyHasInput() const; - - std::string dump() const; - -private: - // True if touch resampling is enabled. - const bool mResampleTouch; - - std::shared_ptr mChannel; - - // The current input message. - InputMessage mMsg; - - // True if mMsg contains a valid input message that was deferred from the previous - // call to consume and that still needs to be handled. - bool mMsgDeferred; - - // Batched motion events per device and source. - struct Batch { - std::vector samples; - }; - std::vector mBatches; - - // Touch state per device and source, only for sources of class pointer. - struct History { - nsecs_t eventTime; - BitSet32 idBits; - int32_t idToIndex[MAX_POINTER_ID + 1]; - PointerCoords pointers[MAX_POINTERS]; - - void initializeFrom(const InputMessage& msg) { - eventTime = msg.body.motion.eventTime; - idBits.clear(); - for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { - uint32_t id = msg.body.motion.pointers[i].properties.id; - idBits.markBit(id); - idToIndex[id] = i; - pointers[i].copyFrom(msg.body.motion.pointers[i].coords); - } - } - - void initializeFrom(const History& other) { - eventTime = other.eventTime; - idBits = other.idBits; // temporary copy - for (size_t i = 0; i < other.idBits.count(); i++) { - uint32_t id = idBits.clearFirstMarkedBit(); - int32_t index = other.idToIndex[id]; - idToIndex[id] = index; - pointers[index].copyFrom(other.pointers[index]); - } - idBits = other.idBits; // final copy - } - - const PointerCoords& getPointerById(uint32_t id) const { - return pointers[idToIndex[id]]; - } - - bool hasPointerId(uint32_t id) const { - return idBits.hasBit(id); - } - }; - struct TouchState { - int32_t deviceId; - int32_t source; - size_t historyCurrent; - size_t historySize; - History history[2]; - History lastResample; - - void initialize(int32_t deviceId, int32_t source) { - this->deviceId = deviceId; - this->source = source; - historyCurrent = 0; - historySize = 0; - lastResample.eventTime = 0; - lastResample.idBits.clear(); - } - - void addHistory(const InputMessage& msg) { - historyCurrent ^= 1; - if (historySize < 2) { - historySize += 1; - } - history[historyCurrent].initializeFrom(msg); - } - - const History* getHistory(size_t index) const { - return &history[(historyCurrent + index) & 1]; - } - - bool recentCoordinatesAreIdentical(uint32_t id) const { - // Return true if the two most recently received "raw" coordinates are identical - if (historySize < 2) { - return false; - } - if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) { - return false; - } - float currentX = getHistory(0)->getPointerById(id).getX(); - float currentY = getHistory(0)->getPointerById(id).getY(); - float previousX = getHistory(1)->getPointerById(id).getX(); - float previousY = getHistory(1)->getPointerById(id).getY(); - if (currentX == previousX && currentY == previousY) { - return true; - } - return false; - } - }; - std::vector mTouchStates; - - // Chain of batched sequence numbers. When multiple input messages are combined into - // a batch, we append a record here that associates the last sequence number in the - // batch with the previous one. When the finished signal is sent, we traverse the - // chain to individually finish all input messages that were part of the batch. - struct SeqChain { - uint32_t seq; // sequence number of batched input message - uint32_t chain; // sequence number of previous batched input message - }; - std::vector mSeqChains; - - // The time at which each event with the sequence number 'seq' was consumed. - // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency - // This collection is populated when the event is received, and the entries are erased when the - // events are finished. It should not grow infinitely because if an event is not ack'd, ANR - // will be raised for that connection, and no further events will be posted to that channel. - std::unordered_map mConsumeTimes; - - status_t consumeBatch(InputEventFactoryInterface* factory, - nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent); - status_t consumeSamples(InputEventFactoryInterface* factory, - Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent); - - void updateTouchState(InputMessage& msg); - void resampleTouchState(nsecs_t frameTime, MotionEvent* event, - const InputMessage *next); - - ssize_t findBatch(int32_t deviceId, int32_t source) const; - ssize_t findTouchState(int32_t deviceId, int32_t source) const; - - nsecs_t getConsumeTime(uint32_t seq) const; - void popConsumeTime(uint32_t seq); - status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled); - - static void rewriteMessage(TouchState& state, InputMessage& msg); - static bool canAddSample(const Batch& batch, const InputMessage* msg); - static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time); - - static bool isTouchResamplingEnabled(); -}; - } // namespace android diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index a9d6e8d3bf..9791212e07 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 6b8bc01c24..65e93a9a87 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -204,6 +204,7 @@ cc_library { srcs: [ "AccelerationCurve.cpp", "Input.cpp", + "InputConsumer.cpp", "InputDevice.cpp", "InputEventLabels.cpp", "InputTransport.cpp", diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp new file mode 100644 index 0000000000..e0d874ef76 --- /dev/null +++ b/libs/input/InputConsumer.cpp @@ -0,0 +1,939 @@ +/** + * Copyright 2024 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. + */ + +#define LOG_TAG "InputTransport" +#define ATRACE_TAG ATRACE_TAG_INPUT + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace input_flags = com::android::input::flags; + +namespace android { + +namespace { + +/** + * Log debug messages relating to the consumer end of the transport channel. + * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart) + */ + +const bool DEBUG_TRANSPORT_CONSUMER = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO); + +const bool IS_DEBUGGABLE_BUILD = +#if defined(__ANDROID__) + android::base::GetBoolProperty("ro.debuggable", false); +#else + true; +#endif + +/** + * Log debug messages about touch event resampling. + * + * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG". + * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately + * on debuggable builds (e.g. userdebug). + */ +bool debugResampling() { + if (!IS_DEBUGGABLE_BUILD) { + static const bool DEBUG_TRANSPORT_RESAMPLING = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", + ANDROID_LOG_INFO); + return DEBUG_TRANSPORT_RESAMPLING; + } + return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO); +} + +void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) { + event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, + msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, + msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, + msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, + msg.body.key.eventTime); +} + +void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) { + event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus); +} + +void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) { + event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled); +} + +void initializeDragEvent(DragEvent& event, const InputMessage& msg) { + event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y, + msg.body.drag.isExiting); +} + +void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { + uint32_t pointerCount = msg.body.motion.pointerCount; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + for (uint32_t i = 0; i < pointerCount; i++) { + pointerProperties[i] = msg.body.motion.pointers[i].properties; + pointerCoords[i] = msg.body.motion.pointers[i].coords; + } + + ui::Transform transform; + transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx, + msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1}); + ui::Transform displayTransform; + displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw, + msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, + 0, 0, 1}); + event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, + msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, + msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, + msg.body.motion.metaState, msg.body.motion.buttonState, + msg.body.motion.classification, transform, msg.body.motion.xPrecision, + msg.body.motion.yPrecision, msg.body.motion.xCursorPosition, + msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime, + msg.body.motion.eventTime, pointerCount, pointerProperties, pointerCoords); +} + +void addSample(MotionEvent& event, const InputMessage& msg) { + uint32_t pointerCount = msg.body.motion.pointerCount; + PointerCoords pointerCoords[pointerCount]; + for (uint32_t i = 0; i < pointerCount; i++) { + pointerCoords[i] = msg.body.motion.pointers[i].coords; + } + + event.setMetaState(event.getMetaState() | msg.body.motion.metaState); + event.addSample(msg.body.motion.eventTime, pointerCoords); +} + +void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) { + event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); +} + +// Nanoseconds per milliseconds. +constexpr nsecs_t NANOS_PER_MS = 1000000; + +// Latency added during resampling. A few milliseconds doesn't hurt much but +// reduces the impact of mispredicted touch positions. +const std::chrono::duration RESAMPLE_LATENCY = 5ms; + +// Minimum time difference between consecutive samples before attempting to resample. +const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS; + +// Maximum time difference between consecutive samples before attempting to resample +// by extrapolation. +const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS; + +// Maximum time to predict forward from the last known state, to avoid predicting too +// far into the future. This time is further bounded by 50% of the last time delta. +const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS; + +/** + * System property for enabling / disabling touch resampling. + * Resampling extrapolates / interpolates the reported touch event coordinates to better + * align them to the VSYNC signal, thus resulting in smoother scrolling performance. + * Resampling is not needed (and should be disabled) on hardware that already + * has touch events triggered by VSYNC. + * Set to "1" to enable resampling (default). + * Set to "0" to disable resampling. + * Resampling is enabled by default. + */ +const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling"; + +inline float lerp(float a, float b, float alpha) { + return a + alpha * (b - a); +} + +inline bool isPointerEvent(int32_t source) { + return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER; +} + +bool shouldResampleTool(ToolType toolType) { + return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN; +} + +} // namespace + +using android::base::Result; +using android::base::StringPrintf; + +// --- InputConsumer --- + +InputConsumer::InputConsumer(const std::shared_ptr& channel) + : InputConsumer(channel, isTouchResamplingEnabled()) {} + +InputConsumer::InputConsumer(const std::shared_ptr& channel, + bool enableTouchResampling) + : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {} + +InputConsumer::~InputConsumer() {} + +bool InputConsumer::isTouchResamplingEnabled() { + return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true); +} + +status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches, + nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) { + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64, + mChannel->getName().c_str(), toString(consumeBatches), frameTime); + + *outSeq = 0; + *outEvent = nullptr; + + // Fetch the next input message. + // Loop until an event can be returned or no additional events are received. + while (!*outEvent) { + if (mMsgDeferred) { + // mMsg contains a valid input message from the previous call to consume + // that has not yet been processed. + mMsgDeferred = false; + } else { + // Receive a fresh message. + status_t result = mChannel->receiveMessage(&mMsg); + if (result == OK) { + const auto [_, inserted] = + mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC)); + LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32, + mMsg.header.seq); + + // Trace the event processing timeline - event was just read from the socket + ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq); + } + if (result) { + // Consume the next batched event unless batches are being held for later. + if (consumeBatches || result != WOULD_BLOCK) { + result = consumeBatch(factory, frameTime, outSeq, outEvent); + if (*outEvent) { + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed batch event, seq=%u", + mChannel->getName().c_str(), *outSeq); + break; + } + } + return result; + } + } + + switch (mMsg.header.type) { + case InputMessage::Type::KEY: { + KeyEvent* keyEvent = factory->createKeyEvent(); + if (!keyEvent) return NO_MEMORY; + + initializeKeyEvent(*keyEvent, mMsg); + *outSeq = mMsg.header.seq; + *outEvent = keyEvent; + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed key event, seq=%u", + mChannel->getName().c_str(), *outSeq); + break; + } + + case InputMessage::Type::MOTION: { + ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source); + if (batchIndex >= 0) { + Batch& batch = mBatches[batchIndex]; + if (canAddSample(batch, &mMsg)) { + batch.samples.push_back(mMsg); + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ appended to batch event", + mChannel->getName().c_str()); + break; + } else if (isPointerEvent(mMsg.body.motion.source) && + mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) { + // No need to process events that we are going to cancel anyways + const size_t count = batch.samples.size(); + for (size_t i = 0; i < count; i++) { + const InputMessage& msg = batch.samples[i]; + sendFinishedSignal(msg.header.seq, false); + } + batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count); + mBatches.erase(mBatches.begin() + batchIndex); + } else { + // We cannot append to the batch in progress, so we need to consume + // the previous batch right now and defer the new message until later. + mMsgDeferred = true; + status_t result = consumeSamples(factory, batch, batch.samples.size(), + outSeq, outEvent); + mBatches.erase(mBatches.begin() + batchIndex); + if (result) { + return result; + } + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed batch event and " + "deferred current event, seq=%u", + mChannel->getName().c_str(), *outSeq); + break; + } + } + + // Start a new batch if needed. + if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE || + mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) { + Batch batch; + batch.samples.push_back(mMsg); + mBatches.push_back(batch); + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ started batch event", + mChannel->getName().c_str()); + break; + } + + MotionEvent* motionEvent = factory->createMotionEvent(); + if (!motionEvent) return NO_MEMORY; + + updateTouchState(mMsg); + initializeMotionEvent(*motionEvent, mMsg); + *outSeq = mMsg.header.seq; + *outEvent = motionEvent; + + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ consumed motion event, seq=%u", + mChannel->getName().c_str(), *outSeq); + break; + } + + case InputMessage::Type::FINISHED: + case InputMessage::Type::TIMELINE: { + LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by " + "InputConsumer!", + ftl::enum_string(mMsg.header.type).c_str()); + break; + } + + case InputMessage::Type::FOCUS: { + FocusEvent* focusEvent = factory->createFocusEvent(); + if (!focusEvent) return NO_MEMORY; + + initializeFocusEvent(*focusEvent, mMsg); + *outSeq = mMsg.header.seq; + *outEvent = focusEvent; + break; + } + + case InputMessage::Type::CAPTURE: { + CaptureEvent* captureEvent = factory->createCaptureEvent(); + if (!captureEvent) return NO_MEMORY; + + initializeCaptureEvent(*captureEvent, mMsg); + *outSeq = mMsg.header.seq; + *outEvent = captureEvent; + break; + } + + case InputMessage::Type::DRAG: { + DragEvent* dragEvent = factory->createDragEvent(); + if (!dragEvent) return NO_MEMORY; + + initializeDragEvent(*dragEvent, mMsg); + *outSeq = mMsg.header.seq; + *outEvent = dragEvent; + break; + } + + case InputMessage::Type::TOUCH_MODE: { + TouchModeEvent* touchModeEvent = factory->createTouchModeEvent(); + if (!touchModeEvent) return NO_MEMORY; + + initializeTouchModeEvent(*touchModeEvent, mMsg); + *outSeq = mMsg.header.seq; + *outEvent = touchModeEvent; + break; + } + } + } + return OK; +} + +status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime, + uint32_t* outSeq, InputEvent** outEvent) { + status_t result; + for (size_t i = mBatches.size(); i > 0;) { + i--; + Batch& batch = mBatches[i]; + if (frameTime < 0) { + result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent); + mBatches.erase(mBatches.begin() + i); + return result; + } + + nsecs_t sampleTime = frameTime; + if (mResampleTouch) { + sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count(); + } + ssize_t split = findSampleNoLaterThan(batch, sampleTime); + if (split < 0) { + continue; + } + + result = consumeSamples(factory, batch, split + 1, outSeq, outEvent); + const InputMessage* next; + if (batch.samples.empty()) { + mBatches.erase(mBatches.begin() + i); + next = nullptr; + } else { + next = &batch.samples[0]; + } + if (!result && mResampleTouch) { + resampleTouchState(sampleTime, static_cast(*outEvent), next); + } + return result; + } + + return WOULD_BLOCK; +} + +status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, Batch& batch, + size_t count, uint32_t* outSeq, InputEvent** outEvent) { + MotionEvent* motionEvent = factory->createMotionEvent(); + if (!motionEvent) return NO_MEMORY; + + uint32_t chain = 0; + for (size_t i = 0; i < count; i++) { + InputMessage& msg = batch.samples[i]; + updateTouchState(msg); + if (i) { + SeqChain seqChain; + seqChain.seq = msg.header.seq; + seqChain.chain = chain; + mSeqChains.push_back(seqChain); + addSample(*motionEvent, msg); + } else { + initializeMotionEvent(*motionEvent, msg); + } + chain = msg.header.seq; + } + batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count); + + *outSeq = chain; + *outEvent = motionEvent; + return OK; +} + +void InputConsumer::updateTouchState(InputMessage& msg) { + if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) { + return; + } + + int32_t deviceId = msg.body.motion.deviceId; + int32_t source = msg.body.motion.source; + + // Update the touch state history to incorporate the new input message. + // If the message is in the past relative to the most recently produced resampled + // touch, then use the resampled time and coordinates instead. + switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) { + case AMOTION_EVENT_ACTION_DOWN: { + ssize_t index = findTouchState(deviceId, source); + if (index < 0) { + mTouchStates.push_back({}); + index = mTouchStates.size() - 1; + } + TouchState& touchState = mTouchStates[index]; + touchState.initialize(deviceId, source); + touchState.addHistory(msg); + break; + } + + case AMOTION_EVENT_ACTION_MOVE: { + ssize_t index = findTouchState(deviceId, source); + if (index >= 0) { + TouchState& touchState = mTouchStates[index]; + touchState.addHistory(msg); + rewriteMessage(touchState, msg); + } + break; + } + + case AMOTION_EVENT_ACTION_POINTER_DOWN: { + ssize_t index = findTouchState(deviceId, source); + if (index >= 0) { + TouchState& touchState = mTouchStates[index]; + touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId()); + rewriteMessage(touchState, msg); + } + break; + } + + case AMOTION_EVENT_ACTION_POINTER_UP: { + ssize_t index = findTouchState(deviceId, source); + if (index >= 0) { + TouchState& touchState = mTouchStates[index]; + rewriteMessage(touchState, msg); + touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId()); + } + break; + } + + case AMOTION_EVENT_ACTION_SCROLL: { + ssize_t index = findTouchState(deviceId, source); + if (index >= 0) { + TouchState& touchState = mTouchStates[index]; + rewriteMessage(touchState, msg); + } + break; + } + + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_CANCEL: { + ssize_t index = findTouchState(deviceId, source); + if (index >= 0) { + TouchState& touchState = mTouchStates[index]; + rewriteMessage(touchState, msg); + mTouchStates.erase(mTouchStates.begin() + index); + } + break; + } + } +} + +/** + * Replace the coordinates in msg with the coordinates in lastResample, if necessary. + * + * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time + * is in the past relative to msg and the past two events do not contain identical coordinates), + * then invalidate the lastResample data for that pointer. + * If the two past events have identical coordinates, then lastResample data for that pointer will + * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is + * resampled to the new value x1, then x1 will always be used to replace x0 until some new value + * not equal to x0 is received. + */ +void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) { + nsecs_t eventTime = msg.body.motion.eventTime; + for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { + uint32_t id = msg.body.motion.pointers[i].properties.id; + if (state.lastResample.idBits.hasBit(id)) { + if (eventTime < state.lastResample.eventTime || + state.recentCoordinatesAreIdentical(id)) { + PointerCoords& msgCoords = msg.body.motion.pointers[i].coords; + const PointerCoords& resampleCoords = state.lastResample.getPointerById(id); + ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, + resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(), + msgCoords.getY()); + msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX()); + msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY()); + msgCoords.isResampled = true; + } else { + state.lastResample.idBits.clearBit(id); + } + } + } +} + +void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, + const InputMessage* next) { + if (!mResampleTouch || !(isPointerEvent(event->getSource())) || + event->getAction() != AMOTION_EVENT_ACTION_MOVE) { + return; + } + + ssize_t index = findTouchState(event->getDeviceId(), event->getSource()); + if (index < 0) { + ALOGD_IF(debugResampling(), "Not resampled, no touch state for device."); + return; + } + + TouchState& touchState = mTouchStates[index]; + if (touchState.historySize < 1) { + ALOGD_IF(debugResampling(), "Not resampled, no history for device."); + return; + } + + // Ensure that the current sample has all of the pointers that need to be reported. + const History* current = touchState.getHistory(0); + size_t pointerCount = event->getPointerCount(); + for (size_t i = 0; i < pointerCount; i++) { + uint32_t id = event->getPointerId(i); + if (!current->idBits.hasBit(id)) { + ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id); + return; + } + } + + // Find the data to use for resampling. + const History* other; + History future; + float alpha; + if (next) { + // Interpolate between current sample and future sample. + // So current->eventTime <= sampleTime <= future.eventTime. + future.initializeFrom(*next); + other = &future; + nsecs_t delta = future.eventTime - current->eventTime; + if (delta < RESAMPLE_MIN_DELTA) { + ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.", + delta); + return; + } + alpha = float(sampleTime - current->eventTime) / delta; + } else if (touchState.historySize >= 2) { + // Extrapolate future sample using current sample and past sample. + // So other->eventTime <= current->eventTime <= sampleTime. + other = touchState.getHistory(1); + nsecs_t delta = current->eventTime - other->eventTime; + if (delta < RESAMPLE_MIN_DELTA) { + ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.", + delta); + return; + } else if (delta > RESAMPLE_MAX_DELTA) { + ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.", + delta); + return; + } + nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION); + if (sampleTime > maxPredict) { + ALOGD_IF(debugResampling(), + "Sample time is too far in the future, adjusting prediction " + "from %" PRId64 " to %" PRId64 " ns.", + sampleTime - current->eventTime, maxPredict - current->eventTime); + sampleTime = maxPredict; + } + alpha = float(current->eventTime - sampleTime) / delta; + } else { + ALOGD_IF(debugResampling(), "Not resampled, insufficient data."); + return; + } + + if (current->eventTime == sampleTime) { + // Prevents having 2 events with identical times and coordinates. + return; + } + + // Resample touch coordinates. + History oldLastResample; + oldLastResample.initializeFrom(touchState.lastResample); + touchState.lastResample.eventTime = sampleTime; + touchState.lastResample.idBits.clear(); + for (size_t i = 0; i < pointerCount; i++) { + uint32_t id = event->getPointerId(i); + touchState.lastResample.idToIndex[id] = i; + touchState.lastResample.idBits.markBit(id); + if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) { + // We maintain the previously resampled value for this pointer (stored in + // oldLastResample) when the coordinates for this pointer haven't changed since then. + // This way we don't introduce artificial jitter when pointers haven't actually moved. + // The isResampled flag isn't cleared as the values don't reflect what the device is + // actually reporting. + + // We know here that the coordinates for the pointer haven't changed because we + // would've cleared the resampled bit in rewriteMessage if they had. We can't modify + // lastResample in place because the mapping from pointer ID to index may have changed. + touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id); + continue; + } + + PointerCoords& resampledCoords = touchState.lastResample.pointers[i]; + const PointerCoords& currentCoords = current->getPointerById(id); + resampledCoords = currentCoords; + resampledCoords.isResampled = true; + if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) { + const PointerCoords& otherCoords = other->getPointerById(id); + resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, + lerp(currentCoords.getX(), otherCoords.getX(), alpha)); + resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, + lerp(currentCoords.getY(), otherCoords.getY(), alpha)); + ALOGD_IF(debugResampling(), + "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " + "other (%0.3f, %0.3f), alpha %0.3f", + id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), + currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha); + } else { + ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id, + resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), + currentCoords.getY()); + } + } + + event->addSample(sampleTime, touchState.lastResample.pointers); +} + +status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s", + mChannel->getName().c_str(), seq, toString(handled)); + + if (!seq) { + ALOGE("Attempted to send a finished signal with sequence number 0."); + return BAD_VALUE; + } + + // Send finished signals for the batch sequence chain first. + size_t seqChainCount = mSeqChains.size(); + if (seqChainCount) { + uint32_t currentSeq = seq; + uint32_t chainSeqs[seqChainCount]; + size_t chainIndex = 0; + for (size_t i = seqChainCount; i > 0;) { + i--; + const SeqChain& seqChain = mSeqChains[i]; + if (seqChain.seq == currentSeq) { + currentSeq = seqChain.chain; + chainSeqs[chainIndex++] = currentSeq; + mSeqChains.erase(mSeqChains.begin() + i); + } + } + status_t status = OK; + while (!status && chainIndex > 0) { + chainIndex--; + status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled); + } + if (status) { + // An error occurred so at least one signal was not sent, reconstruct the chain. + for (;;) { + SeqChain seqChain; + seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq; + seqChain.chain = chainSeqs[chainIndex]; + mSeqChains.push_back(seqChain); + if (!chainIndex) break; + chainIndex--; + } + return status; + } + } + + // Send finished signal for the last message in the batch. + return sendUnchainedFinishedSignal(seq, handled); +} + +status_t InputConsumer::sendTimeline(int32_t inputEventId, + std::array graphicsTimeline) { + ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, + "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32 + ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64, + mChannel->getName().c_str(), inputEventId, + graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME], + graphicsTimeline[GraphicsTimeline::PRESENT_TIME]); + + InputMessage msg; + msg.header.type = InputMessage::Type::TIMELINE; + msg.header.seq = 0; + msg.body.timeline.eventId = inputEventId; + msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline); + return mChannel->sendMessage(&msg); +} + +nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const { + auto it = mConsumeTimes.find(seq); + // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was + // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed. + LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32, + seq); + return it->second; +} + +void InputConsumer::popConsumeTime(uint32_t seq) { + mConsumeTimes.erase(seq); +} + +status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) { + InputMessage msg; + msg.header.type = InputMessage::Type::FINISHED; + msg.header.seq = seq; + msg.body.finished.handled = handled; + msg.body.finished.consumeTime = getConsumeTime(seq); + status_t result = mChannel->sendMessage(&msg); + if (result == OK) { + // Remove the consume time if the socket write succeeded. We will not need to ack this + // message anymore. If the socket write did not succeed, we will try again and will still + // need consume time. + popConsumeTime(seq); + + // Trace the event processing timeline - event was just finished + ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq); + } + return result; +} + +bool InputConsumer::hasPendingBatch() const { + return !mBatches.empty(); +} + +int32_t InputConsumer::getPendingBatchSource() const { + if (mBatches.empty()) { + return AINPUT_SOURCE_CLASS_NONE; + } + + const Batch& batch = mBatches[0]; + const InputMessage& head = batch.samples[0]; + return head.body.motion.source; +} + +bool InputConsumer::probablyHasInput() const { + return hasPendingBatch() || mChannel->probablyHasInput(); +} + +ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const { + for (size_t i = 0; i < mBatches.size(); i++) { + const Batch& batch = mBatches[i]; + const InputMessage& head = batch.samples[0]; + if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) { + return i; + } + } + return -1; +} + +ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const { + for (size_t i = 0; i < mTouchStates.size(); i++) { + const TouchState& touchState = mTouchStates[i]; + if (touchState.deviceId == deviceId && touchState.source == source) { + return i; + } + } + return -1; +} + +bool InputConsumer::canAddSample(const Batch& batch, const InputMessage* msg) { + const InputMessage& head = batch.samples[0]; + uint32_t pointerCount = msg->body.motion.pointerCount; + if (head.body.motion.pointerCount != pointerCount || + head.body.motion.action != msg->body.motion.action) { + return false; + } + for (size_t i = 0; i < pointerCount; i++) { + if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) { + return false; + } + } + return true; +} + +ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) { + size_t numSamples = batch.samples.size(); + size_t index = 0; + while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) { + index += 1; + } + return ssize_t(index) - 1; +} + +std::string InputConsumer::dump() const { + std::string out; + out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n"; + out = out + "mChannel = " + mChannel->getName() + "\n"; + out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n"; + if (mMsgDeferred) { + out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n"; + } + out += "Batches:\n"; + for (const Batch& batch : mBatches) { + out += " Batch:\n"; + for (const InputMessage& msg : batch.samples) { + out += android::base::StringPrintf(" Message %" PRIu32 ": %s ", msg.header.seq, + ftl::enum_string(msg.header.type).c_str()); + switch (msg.header.type) { + case InputMessage::Type::KEY: { + out += android::base::StringPrintf("action=%s keycode=%" PRId32, + KeyEvent::actionToString( + msg.body.key.action), + msg.body.key.keyCode); + break; + } + case InputMessage::Type::MOTION: { + out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action); + for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { + const float x = msg.body.motion.pointers[i].coords.getX(); + const float y = msg.body.motion.pointers[i].coords.getY(); + out += android::base::StringPrintf("\n Pointer %" PRIu32 + " : x=%.1f y=%.1f", + i, x, y); + } + break; + } + case InputMessage::Type::FINISHED: { + out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64, + toString(msg.body.finished.handled), + msg.body.finished.consumeTime); + break; + } + case InputMessage::Type::FOCUS: { + out += android::base::StringPrintf("hasFocus=%s", + toString(msg.body.focus.hasFocus)); + break; + } + case InputMessage::Type::CAPTURE: { + out += android::base::StringPrintf("hasCapture=%s", + toString(msg.body.capture + .pointerCaptureEnabled)); + break; + } + case InputMessage::Type::DRAG: { + out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s", + msg.body.drag.x, msg.body.drag.y, + toString(msg.body.drag.isExiting)); + break; + } + case InputMessage::Type::TIMELINE: { + const nsecs_t gpuCompletedTime = + msg.body.timeline + .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]; + const nsecs_t presentTime = + msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]; + out += android::base::StringPrintf("inputEventId=%" PRId32 + ", gpuCompletedTime=%" PRId64 + ", presentTime=%" PRId64, + msg.body.timeline.eventId, gpuCompletedTime, + presentTime); + break; + } + case InputMessage::Type::TOUCH_MODE: { + out += android::base::StringPrintf("isInTouchMode=%s", + toString(msg.body.touchMode.isInTouchMode)); + break; + } + } + out += "\n"; + } + } + if (mBatches.empty()) { + out += " \n"; + } + out += "mSeqChains:\n"; + for (const SeqChain& chain : mSeqChains) { + out += android::base::StringPrintf(" chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq, + chain.chain); + } + if (mSeqChains.empty()) { + out += " \n"; + } + out += "mConsumeTimes:\n"; + for (const auto& [seq, consumeTime] : mConsumeTimes) { + out += android::base::StringPrintf(" seq = %" PRIu32 " consumeTime = %" PRId64, seq, + consumeTime); + } + if (mConsumeTimes.empty()) { + out += " \n"; + } + return out; +} + +} // namespace android diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index b3a36ebf5a..1869483474 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -51,14 +51,6 @@ const bool DEBUG_CHANNEL_MESSAGES = const bool DEBUG_CHANNEL_LIFECYCLE = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO); -/** - * Log debug messages relating to the consumer end of the transport channel. - * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart) - */ - -const bool DEBUG_TRANSPORT_CONSUMER = - __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO); - const bool IS_DEBUGGABLE_BUILD = #if defined(__ANDROID__) android::base::GetBoolProperty("ro.debuggable", false); @@ -81,23 +73,6 @@ bool debugTransportPublisher() { return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO); } -/** - * Log debug messages about touch event resampling. - * - * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG". - * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately - * on debuggable builds (e.g. userdebug). - */ -bool debugResampling() { - if (!IS_DEBUGGABLE_BUILD) { - static const bool DEBUG_TRANSPORT_RESAMPLING = - __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", - ANDROID_LOG_INFO); - return DEBUG_TRANSPORT_RESAMPLING; - } - return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO); -} - android::base::unique_fd dupChannelFd(int fd) { android::base::unique_fd newFd(::dup(fd)); if (!newFd.ok()) { @@ -113,103 +88,11 @@ android::base::unique_fd dupChannelFd(int fd) { return newFd; } -void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) { - event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, - msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, - msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, - msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, - msg.body.key.eventTime); -} - -void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) { - event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus); -} - -void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) { - event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled); -} - -void initializeDragEvent(DragEvent& event, const InputMessage& msg) { - event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y, - msg.body.drag.isExiting); -} - -void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { - uint32_t pointerCount = msg.body.motion.pointerCount; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - for (uint32_t i = 0; i < pointerCount; i++) { - pointerProperties[i] = msg.body.motion.pointers[i].properties; - pointerCoords[i] = msg.body.motion.pointers[i].coords; - } - - ui::Transform transform; - transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx, - msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1}); - ui::Transform displayTransform; - displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw, - msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, - 0, 0, 1}); - event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, - msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, - msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, - msg.body.motion.metaState, msg.body.motion.buttonState, - msg.body.motion.classification, transform, msg.body.motion.xPrecision, - msg.body.motion.yPrecision, msg.body.motion.xCursorPosition, - msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime, - msg.body.motion.eventTime, pointerCount, pointerProperties, pointerCoords); -} - -void addSample(MotionEvent& event, const InputMessage& msg) { - uint32_t pointerCount = msg.body.motion.pointerCount; - PointerCoords pointerCoords[pointerCount]; - for (uint32_t i = 0; i < pointerCount; i++) { - pointerCoords[i] = msg.body.motion.pointers[i].coords; - } - - event.setMetaState(event.getMetaState() | msg.body.motion.metaState); - event.addSample(msg.body.motion.eventTime, pointerCoords); -} - -void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) { - event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); -} - // Socket buffer size. The default is typically about 128KB, which is much larger than // we really need. So we make it smaller. It just needs to be big enough to hold // a few dozen large multi-finger motion events in the case where an application gets // behind processing touches. -static constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024; - -// Nanoseconds per milliseconds. -static constexpr nsecs_t NANOS_PER_MS = 1000000; - -// Latency added during resampling. A few milliseconds doesn't hurt much but -// reduces the impact of mispredicted touch positions. -const std::chrono::duration RESAMPLE_LATENCY = 5ms; - -// Minimum time difference between consecutive samples before attempting to resample. -static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS; - -// Maximum time difference between consecutive samples before attempting to resample -// by extrapolation. -static const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS; - -// Maximum time to predict forward from the last known state, to avoid predicting too -// far into the future. This time is further bounded by 50% of the last time delta. -static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS; - -/** - * System property for enabling / disabling touch resampling. - * Resampling extrapolates / interpolates the reported touch event coordinates to better - * align them to the VSYNC signal, thus resulting in smoother scrolling performance. - * Resampling is not needed (and should be disabled) on hardware that already - * has touch events triggered by VSYNC. - * Set to "1" to enable resampling (default). - * Set to "0" to disable resampling. - * Resampling is enabled by default. - */ -static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling"; +constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024; /** * Crash if the events that are getting sent to the InputPublisher are inconsistent. @@ -220,18 +103,6 @@ bool verifyEvents() { __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO); } -inline float lerp(float a, float b, float alpha) { - return a + alpha * (b - a); -} - -inline bool isPointerEvent(int32_t source) { - return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER; -} - -bool shouldResampleTool(ToolType toolType) { - return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN; -} - } // namespace using android::base::Result; @@ -892,756 +763,4 @@ android::base::Result InputPublisher::receiveC return android::base::Error(UNKNOWN_ERROR); } -// --- InputConsumer --- - -InputConsumer::InputConsumer(const std::shared_ptr& channel) - : InputConsumer(channel, isTouchResamplingEnabled()) {} - -InputConsumer::InputConsumer(const std::shared_ptr& channel, - bool enableTouchResampling) - : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {} - -InputConsumer::~InputConsumer() { -} - -bool InputConsumer::isTouchResamplingEnabled() { - return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true); -} - -status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches, - nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) { - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64, - mChannel->getName().c_str(), toString(consumeBatches), frameTime); - - *outSeq = 0; - *outEvent = nullptr; - - // Fetch the next input message. - // Loop until an event can be returned or no additional events are received. - while (!*outEvent) { - if (mMsgDeferred) { - // mMsg contains a valid input message from the previous call to consume - // that has not yet been processed. - mMsgDeferred = false; - } else { - // Receive a fresh message. - status_t result = mChannel->receiveMessage(&mMsg); - if (result == OK) { - const auto [_, inserted] = - mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC)); - LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32, - mMsg.header.seq); - - // Trace the event processing timeline - event was just read from the socket - ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq); - } - if (result) { - // Consume the next batched event unless batches are being held for later. - if (consumeBatches || result != WOULD_BLOCK) { - result = consumeBatch(factory, frameTime, outSeq, outEvent); - if (*outEvent) { - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ consumed batch event, seq=%u", - mChannel->getName().c_str(), *outSeq); - break; - } - } - return result; - } - } - - switch (mMsg.header.type) { - case InputMessage::Type::KEY: { - KeyEvent* keyEvent = factory->createKeyEvent(); - if (!keyEvent) return NO_MEMORY; - - initializeKeyEvent(*keyEvent, mMsg); - *outSeq = mMsg.header.seq; - *outEvent = keyEvent; - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ consumed key event, seq=%u", - mChannel->getName().c_str(), *outSeq); - break; - } - - case InputMessage::Type::MOTION: { - ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source); - if (batchIndex >= 0) { - Batch& batch = mBatches[batchIndex]; - if (canAddSample(batch, &mMsg)) { - batch.samples.push_back(mMsg); - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ appended to batch event", - mChannel->getName().c_str()); - break; - } else if (isPointerEvent(mMsg.body.motion.source) && - mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) { - // No need to process events that we are going to cancel anyways - const size_t count = batch.samples.size(); - for (size_t i = 0; i < count; i++) { - const InputMessage& msg = batch.samples[i]; - sendFinishedSignal(msg.header.seq, false); - } - batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count); - mBatches.erase(mBatches.begin() + batchIndex); - } else { - // We cannot append to the batch in progress, so we need to consume - // the previous batch right now and defer the new message until later. - mMsgDeferred = true; - status_t result = consumeSamples(factory, batch, batch.samples.size(), - outSeq, outEvent); - mBatches.erase(mBatches.begin() + batchIndex); - if (result) { - return result; - } - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ consumed batch event and " - "deferred current event, seq=%u", - mChannel->getName().c_str(), *outSeq); - break; - } - } - - // Start a new batch if needed. - if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE || - mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) { - Batch batch; - batch.samples.push_back(mMsg); - mBatches.push_back(batch); - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ started batch event", - mChannel->getName().c_str()); - break; - } - - MotionEvent* motionEvent = factory->createMotionEvent(); - if (!motionEvent) return NO_MEMORY; - - updateTouchState(mMsg); - initializeMotionEvent(*motionEvent, mMsg); - *outSeq = mMsg.header.seq; - *outEvent = motionEvent; - - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ consumed motion event, seq=%u", - mChannel->getName().c_str(), *outSeq); - break; - } - - case InputMessage::Type::FINISHED: - case InputMessage::Type::TIMELINE: { - LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by " - "InputConsumer!", - ftl::enum_string(mMsg.header.type).c_str()); - break; - } - - case InputMessage::Type::FOCUS: { - FocusEvent* focusEvent = factory->createFocusEvent(); - if (!focusEvent) return NO_MEMORY; - - initializeFocusEvent(*focusEvent, mMsg); - *outSeq = mMsg.header.seq; - *outEvent = focusEvent; - break; - } - - case InputMessage::Type::CAPTURE: { - CaptureEvent* captureEvent = factory->createCaptureEvent(); - if (!captureEvent) return NO_MEMORY; - - initializeCaptureEvent(*captureEvent, mMsg); - *outSeq = mMsg.header.seq; - *outEvent = captureEvent; - break; - } - - case InputMessage::Type::DRAG: { - DragEvent* dragEvent = factory->createDragEvent(); - if (!dragEvent) return NO_MEMORY; - - initializeDragEvent(*dragEvent, mMsg); - *outSeq = mMsg.header.seq; - *outEvent = dragEvent; - break; - } - - case InputMessage::Type::TOUCH_MODE: { - TouchModeEvent* touchModeEvent = factory->createTouchModeEvent(); - if (!touchModeEvent) return NO_MEMORY; - - initializeTouchModeEvent(*touchModeEvent, mMsg); - *outSeq = mMsg.header.seq; - *outEvent = touchModeEvent; - break; - } - } - } - return OK; -} - -status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, - nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) { - status_t result; - for (size_t i = mBatches.size(); i > 0; ) { - i--; - Batch& batch = mBatches[i]; - if (frameTime < 0) { - result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent); - mBatches.erase(mBatches.begin() + i); - return result; - } - - nsecs_t sampleTime = frameTime; - if (mResampleTouch) { - sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count(); - } - ssize_t split = findSampleNoLaterThan(batch, sampleTime); - if (split < 0) { - continue; - } - - result = consumeSamples(factory, batch, split + 1, outSeq, outEvent); - const InputMessage* next; - if (batch.samples.empty()) { - mBatches.erase(mBatches.begin() + i); - next = nullptr; - } else { - next = &batch.samples[0]; - } - if (!result && mResampleTouch) { - resampleTouchState(sampleTime, static_cast(*outEvent), next); - } - return result; - } - - return WOULD_BLOCK; -} - -status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, - Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) { - MotionEvent* motionEvent = factory->createMotionEvent(); - if (! motionEvent) return NO_MEMORY; - - uint32_t chain = 0; - for (size_t i = 0; i < count; i++) { - InputMessage& msg = batch.samples[i]; - updateTouchState(msg); - if (i) { - SeqChain seqChain; - seqChain.seq = msg.header.seq; - seqChain.chain = chain; - mSeqChains.push_back(seqChain); - addSample(*motionEvent, msg); - } else { - initializeMotionEvent(*motionEvent, msg); - } - chain = msg.header.seq; - } - batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count); - - *outSeq = chain; - *outEvent = motionEvent; - return OK; -} - -void InputConsumer::updateTouchState(InputMessage& msg) { - if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) { - return; - } - - int32_t deviceId = msg.body.motion.deviceId; - int32_t source = msg.body.motion.source; - - // Update the touch state history to incorporate the new input message. - // If the message is in the past relative to the most recently produced resampled - // touch, then use the resampled time and coordinates instead. - switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) { - case AMOTION_EVENT_ACTION_DOWN: { - ssize_t index = findTouchState(deviceId, source); - if (index < 0) { - mTouchStates.push_back({}); - index = mTouchStates.size() - 1; - } - TouchState& touchState = mTouchStates[index]; - touchState.initialize(deviceId, source); - touchState.addHistory(msg); - break; - } - - case AMOTION_EVENT_ACTION_MOVE: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates[index]; - touchState.addHistory(msg); - rewriteMessage(touchState, msg); - } - break; - } - - case AMOTION_EVENT_ACTION_POINTER_DOWN: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates[index]; - touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId()); - rewriteMessage(touchState, msg); - } - break; - } - - case AMOTION_EVENT_ACTION_POINTER_UP: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates[index]; - rewriteMessage(touchState, msg); - touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId()); - } - break; - } - - case AMOTION_EVENT_ACTION_SCROLL: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates[index]; - rewriteMessage(touchState, msg); - } - break; - } - - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_CANCEL: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates[index]; - rewriteMessage(touchState, msg); - mTouchStates.erase(mTouchStates.begin() + index); - } - break; - } - } -} - -/** - * Replace the coordinates in msg with the coordinates in lastResample, if necessary. - * - * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time - * is in the past relative to msg and the past two events do not contain identical coordinates), - * then invalidate the lastResample data for that pointer. - * If the two past events have identical coordinates, then lastResample data for that pointer will - * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is - * resampled to the new value x1, then x1 will always be used to replace x0 until some new value - * not equal to x0 is received. - */ -void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) { - nsecs_t eventTime = msg.body.motion.eventTime; - for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { - uint32_t id = msg.body.motion.pointers[i].properties.id; - if (state.lastResample.idBits.hasBit(id)) { - if (eventTime < state.lastResample.eventTime || - state.recentCoordinatesAreIdentical(id)) { - PointerCoords& msgCoords = msg.body.motion.pointers[i].coords; - const PointerCoords& resampleCoords = state.lastResample.getPointerById(id); - ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, - resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(), - msgCoords.getY()); - msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX()); - msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY()); - msgCoords.isResampled = true; - } else { - state.lastResample.idBits.clearBit(id); - } - } - } -} - -void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, - const InputMessage* next) { - if (!mResampleTouch - || !(isPointerEvent(event->getSource())) - || event->getAction() != AMOTION_EVENT_ACTION_MOVE) { - return; - } - - ssize_t index = findTouchState(event->getDeviceId(), event->getSource()); - if (index < 0) { - ALOGD_IF(debugResampling(), "Not resampled, no touch state for device."); - return; - } - - TouchState& touchState = mTouchStates[index]; - if (touchState.historySize < 1) { - ALOGD_IF(debugResampling(), "Not resampled, no history for device."); - return; - } - - // Ensure that the current sample has all of the pointers that need to be reported. - const History* current = touchState.getHistory(0); - size_t pointerCount = event->getPointerCount(); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t id = event->getPointerId(i); - if (!current->idBits.hasBit(id)) { - ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id); - return; - } - } - - // Find the data to use for resampling. - const History* other; - History future; - float alpha; - if (next) { - // Interpolate between current sample and future sample. - // So current->eventTime <= sampleTime <= future.eventTime. - future.initializeFrom(*next); - other = &future; - nsecs_t delta = future.eventTime - current->eventTime; - if (delta < RESAMPLE_MIN_DELTA) { - ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.", - delta); - return; - } - alpha = float(sampleTime - current->eventTime) / delta; - } else if (touchState.historySize >= 2) { - // Extrapolate future sample using current sample and past sample. - // So other->eventTime <= current->eventTime <= sampleTime. - other = touchState.getHistory(1); - nsecs_t delta = current->eventTime - other->eventTime; - if (delta < RESAMPLE_MIN_DELTA) { - ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.", - delta); - return; - } else if (delta > RESAMPLE_MAX_DELTA) { - ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.", - delta); - return; - } - nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION); - if (sampleTime > maxPredict) { - ALOGD_IF(debugResampling(), - "Sample time is too far in the future, adjusting prediction " - "from %" PRId64 " to %" PRId64 " ns.", - sampleTime - current->eventTime, maxPredict - current->eventTime); - sampleTime = maxPredict; - } - alpha = float(current->eventTime - sampleTime) / delta; - } else { - ALOGD_IF(debugResampling(), "Not resampled, insufficient data."); - return; - } - - if (current->eventTime == sampleTime) { - // Prevents having 2 events with identical times and coordinates. - return; - } - - // Resample touch coordinates. - History oldLastResample; - oldLastResample.initializeFrom(touchState.lastResample); - touchState.lastResample.eventTime = sampleTime; - touchState.lastResample.idBits.clear(); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t id = event->getPointerId(i); - touchState.lastResample.idToIndex[id] = i; - touchState.lastResample.idBits.markBit(id); - if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) { - // We maintain the previously resampled value for this pointer (stored in - // oldLastResample) when the coordinates for this pointer haven't changed since then. - // This way we don't introduce artificial jitter when pointers haven't actually moved. - // The isResampled flag isn't cleared as the values don't reflect what the device is - // actually reporting. - - // We know here that the coordinates for the pointer haven't changed because we - // would've cleared the resampled bit in rewriteMessage if they had. We can't modify - // lastResample in place becasue the mapping from pointer ID to index may have changed. - touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id); - continue; - } - - PointerCoords& resampledCoords = touchState.lastResample.pointers[i]; - const PointerCoords& currentCoords = current->getPointerById(id); - resampledCoords = currentCoords; - resampledCoords.isResampled = true; - if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) { - const PointerCoords& otherCoords = other->getPointerById(id); - resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, - lerp(currentCoords.getX(), otherCoords.getX(), alpha)); - resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, - lerp(currentCoords.getY(), otherCoords.getY(), alpha)); - ALOGD_IF(debugResampling(), - "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " - "other (%0.3f, %0.3f), alpha %0.3f", - id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), - currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha); - } else { - ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id, - resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), - currentCoords.getY()); - } - } - - event->addSample(sampleTime, touchState.lastResample.pointers); -} - -status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s", - mChannel->getName().c_str(), seq, toString(handled)); - - if (!seq) { - ALOGE("Attempted to send a finished signal with sequence number 0."); - return BAD_VALUE; - } - - // Send finished signals for the batch sequence chain first. - size_t seqChainCount = mSeqChains.size(); - if (seqChainCount) { - uint32_t currentSeq = seq; - uint32_t chainSeqs[seqChainCount]; - size_t chainIndex = 0; - for (size_t i = seqChainCount; i > 0; ) { - i--; - const SeqChain& seqChain = mSeqChains[i]; - if (seqChain.seq == currentSeq) { - currentSeq = seqChain.chain; - chainSeqs[chainIndex++] = currentSeq; - mSeqChains.erase(mSeqChains.begin() + i); - } - } - status_t status = OK; - while (!status && chainIndex > 0) { - chainIndex--; - status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled); - } - if (status) { - // An error occurred so at least one signal was not sent, reconstruct the chain. - for (;;) { - SeqChain seqChain; - seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq; - seqChain.chain = chainSeqs[chainIndex]; - mSeqChains.push_back(seqChain); - if (!chainIndex) break; - chainIndex--; - } - return status; - } - } - - // Send finished signal for the last message in the batch. - return sendUnchainedFinishedSignal(seq, handled); -} - -status_t InputConsumer::sendTimeline(int32_t inputEventId, - std::array graphicsTimeline) { - ALOGD_IF(DEBUG_TRANSPORT_CONSUMER, - "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32 - ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64, - mChannel->getName().c_str(), inputEventId, - graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME], - graphicsTimeline[GraphicsTimeline::PRESENT_TIME]); - - InputMessage msg; - msg.header.type = InputMessage::Type::TIMELINE; - msg.header.seq = 0; - msg.body.timeline.eventId = inputEventId; - msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline); - return mChannel->sendMessage(&msg); -} - -nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const { - auto it = mConsumeTimes.find(seq); - // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was - // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed. - LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32, - seq); - return it->second; -} - -void InputConsumer::popConsumeTime(uint32_t seq) { - mConsumeTimes.erase(seq); -} - -status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) { - InputMessage msg; - msg.header.type = InputMessage::Type::FINISHED; - msg.header.seq = seq; - msg.body.finished.handled = handled; - msg.body.finished.consumeTime = getConsumeTime(seq); - status_t result = mChannel->sendMessage(&msg); - if (result == OK) { - // Remove the consume time if the socket write succeeded. We will not need to ack this - // message anymore. If the socket write did not succeed, we will try again and will still - // need consume time. - popConsumeTime(seq); - - // Trace the event processing timeline - event was just finished - ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq); - } - return result; -} - -bool InputConsumer::hasPendingBatch() const { - return !mBatches.empty(); -} - -int32_t InputConsumer::getPendingBatchSource() const { - if (mBatches.empty()) { - return AINPUT_SOURCE_CLASS_NONE; - } - - const Batch& batch = mBatches[0]; - const InputMessage& head = batch.samples[0]; - return head.body.motion.source; -} - -bool InputConsumer::probablyHasInput() const { - return hasPendingBatch() || mChannel->probablyHasInput(); -} - -ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const { - for (size_t i = 0; i < mBatches.size(); i++) { - const Batch& batch = mBatches[i]; - const InputMessage& head = batch.samples[0]; - if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) { - return i; - } - } - return -1; -} - -ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const { - for (size_t i = 0; i < mTouchStates.size(); i++) { - const TouchState& touchState = mTouchStates[i]; - if (touchState.deviceId == deviceId && touchState.source == source) { - return i; - } - } - return -1; -} - -bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) { - const InputMessage& head = batch.samples[0]; - uint32_t pointerCount = msg->body.motion.pointerCount; - if (head.body.motion.pointerCount != pointerCount - || head.body.motion.action != msg->body.motion.action) { - return false; - } - for (size_t i = 0; i < pointerCount; i++) { - if (head.body.motion.pointers[i].properties - != msg->body.motion.pointers[i].properties) { - return false; - } - } - return true; -} - -ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) { - size_t numSamples = batch.samples.size(); - size_t index = 0; - while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) { - index += 1; - } - return ssize_t(index) - 1; -} - -std::string InputConsumer::dump() const { - std::string out; - out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n"; - out = out + "mChannel = " + mChannel->getName() + "\n"; - out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n"; - if (mMsgDeferred) { - out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n"; - } - out += "Batches:\n"; - for (const Batch& batch : mBatches) { - out += " Batch:\n"; - for (const InputMessage& msg : batch.samples) { - out += android::base::StringPrintf(" Message %" PRIu32 ": %s ", msg.header.seq, - ftl::enum_string(msg.header.type).c_str()); - switch (msg.header.type) { - case InputMessage::Type::KEY: { - out += android::base::StringPrintf("action=%s keycode=%" PRId32, - KeyEvent::actionToString( - msg.body.key.action), - msg.body.key.keyCode); - break; - } - case InputMessage::Type::MOTION: { - out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action); - for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) { - const float x = msg.body.motion.pointers[i].coords.getX(); - const float y = msg.body.motion.pointers[i].coords.getY(); - out += android::base::StringPrintf("\n Pointer %" PRIu32 - " : x=%.1f y=%.1f", - i, x, y); - } - break; - } - case InputMessage::Type::FINISHED: { - out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64, - toString(msg.body.finished.handled), - msg.body.finished.consumeTime); - break; - } - case InputMessage::Type::FOCUS: { - out += android::base::StringPrintf("hasFocus=%s", - toString(msg.body.focus.hasFocus)); - break; - } - case InputMessage::Type::CAPTURE: { - out += android::base::StringPrintf("hasCapture=%s", - toString(msg.body.capture - .pointerCaptureEnabled)); - break; - } - case InputMessage::Type::DRAG: { - out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s", - msg.body.drag.x, msg.body.drag.y, - toString(msg.body.drag.isExiting)); - break; - } - case InputMessage::Type::TIMELINE: { - const nsecs_t gpuCompletedTime = - msg.body.timeline - .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]; - const nsecs_t presentTime = - msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]; - out += android::base::StringPrintf("inputEventId=%" PRId32 - ", gpuCompletedTime=%" PRId64 - ", presentTime=%" PRId64, - msg.body.timeline.eventId, gpuCompletedTime, - presentTime); - break; - } - case InputMessage::Type::TOUCH_MODE: { - out += android::base::StringPrintf("isInTouchMode=%s", - toString(msg.body.touchMode.isInTouchMode)); - break; - } - } - out += "\n"; - } - } - if (mBatches.empty()) { - out += " \n"; - } - out += "mSeqChains:\n"; - for (const SeqChain& chain : mSeqChains) { - out += android::base::StringPrintf(" chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq, - chain.chain); - } - if (mSeqChains.empty()) { - out += " \n"; - } - out += "mConsumeTimes:\n"; - for (const auto& [seq, consumeTime] : mConsumeTimes) { - out += android::base::StringPrintf(" seq = %" PRIu32 " consumeTime = %" PRId64, seq, - consumeTime); - } - if (mConsumeTimes.empty()) { - out += " \n"; - } - return out; -} - } // namespace android diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index b5fab496e2..332831febb 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include using android::base::Result; diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 0b0bb63a81..6e23d4e910 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include using namespace std::chrono_literals; diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h index fe25130bd8..8ce61e7800 100644 --- a/services/inputflinger/tests/FakeWindowHandle.h +++ b/services/inputflinger/tests/FakeWindowHandle.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include "../dispatcher/InputDispatcher.h" using android::base::Result; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index a662198351..423975f75b 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include -- GitLab From a3d2ec8a4c9b51494425d751870620e682d31eb8 Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Wed, 13 Mar 2024 20:45:34 +0000 Subject: [PATCH 069/465] Revert "Fix libtracing_perfetto performance impact" This reverts commit 81deab754e4728f98e09196528e0c3777ca70ed8. Reason for revert: Droid-monitored triggered revert due to likely culprit for breakages in b/329487228. Will be verifying through ABTD before submitting the revert. Change-Id: I83f9eac29fb9f51e2d79053c6e98a047d7ab74d9 --- libs/tracing_perfetto/tracing_perfetto.cpp | 3 +-- .../tracing_perfetto/tracing_perfetto_internal.cpp | 14 +++++++------- libs/tracing_perfetto/tracing_perfetto_internal.h | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index 19d1eb639e..c7fb8bd9a8 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -131,8 +131,7 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } uint64_t getEnabledCategories() { - if (internal::isPerfettoRegistered()) { - // TODO(b/303199244): Return only enabled categories and not all registered ones + if (internal::isPerfettoSdkTracingEnabled()) { return internal::getDefaultCategories(); } else { return atrace_get_enabled_tags(); diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp index 976db7e430..58ba428610 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp +++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp @@ -70,8 +70,6 @@ PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES); PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES); -std::atomic_bool is_perfetto_registered = false; - struct PerfettoTeCategory* toCategory(uint64_t inCategory) { switch (inCategory) { case TRACE_CATEGORY_ALWAYS: @@ -137,11 +135,15 @@ struct PerfettoTeCategory* toCategory(uint64_t inCategory) { } // namespace -bool isPerfettoRegistered() { - return is_perfetto_registered; +bool isPerfettoSdkTracingEnabled() { + return android::os::perfetto_sdk_tracing(); } struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { + if (!isPerfettoSdkTracingEnabled()) { + return nullptr; + } + struct PerfettoTeCategory* perfettoCategory = toCategory(category); bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED)); @@ -149,10 +151,9 @@ struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { } void registerWithPerfetto(bool test) { - if (!android::os::perfetto_sdk_tracing()) { + if (!isPerfettoSdkTracingEnabled()) { return; } - static std::once_flag registration; std::call_once(registration, [test]() { struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); @@ -160,7 +161,6 @@ void registerWithPerfetto(bool test) { PerfettoProducerInit(args); PerfettoTeInit(); PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES); - is_perfetto_registered = true; }); } diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h index 79e4b8f1b4..9a579f162a 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.h +++ b/libs/tracing_perfetto/tracing_perfetto_internal.h @@ -26,7 +26,7 @@ namespace tracing_perfetto { namespace internal { -bool isPerfettoRegistered(); +bool isPerfettoSdkTracingEnabled(); struct PerfettoTeCategory* toPerfettoCategory(uint64_t category); -- GitLab From 17e255148c36a0ee8b3dc28e40e96c491074ddf9 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Wed, 13 Mar 2024 18:02:48 -0400 Subject: [PATCH 070/465] Make AutoBackendTexture remember which context it was created with ... and use that stored context when creating an SkImage or SkSurface. As per I52e1d831f2d195cddb21c2e7a1e820059fe2a137 we no longer share buffers between contexts, and this aligns with Graphite's need to know the context that a backend texture was created with to clean it up in ABT's destructor. Test: manually validated core CUJs + existing tests (refactor) Bug: b/293371537 Bug: others listed in I52e1d831f2d195cddb21c2e7a1e820059fe2a137 Change-Id: I7d4b0b95216b0529be72cfb7c2788c592a77ed0e --- libs/renderengine/skia/AutoBackendTexture.cpp | 14 ++++++-------- libs/renderengine/skia/AutoBackendTexture.h | 15 +++++++-------- libs/renderengine/skia/SkiaRenderEngine.cpp | 5 ++--- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index ee95e59d90..26b280d520 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -37,7 +37,7 @@ namespace skia { AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, CleanupManager& cleanupMgr) - : mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) { + : mGrContext(context), mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) { ATRACE_CALL(); AHardwareBuffer_Desc desc; AHardwareBuffer_describe(buffer, &desc); @@ -158,12 +158,11 @@ void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace } } -sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType, - GrDirectContext* context) { +sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) { ATRACE_CALL(); if (mBackendTexture.isValid()) { - mUpdateProc(mImageCtx, context); + mUpdateProc(mImageCtx, mGrContext); } auto colorType = mColorType; @@ -174,7 +173,7 @@ sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp } sk_sp image = - SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin, + SkImages::BorrowTextureFrom(mGrContext, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, alphaType, toSkColorSpace(dataspace), releaseImageProc, this); if (image.get()) { @@ -190,13 +189,12 @@ sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp return mImage; } -sk_sp AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace, - GrDirectContext* context) { +sk_sp AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) { ATRACE_CALL(); LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture"); if (!mSurface.get() || mDataspace != dataspace) { sk_sp surface = - SkSurfaces::WrapBackendTexture(context, mBackendTexture, + SkSurfaces::WrapBackendTexture(mGrContext, mBackendTexture, kTopLeft_GrSurfaceOrigin, 0, mColorType, toSkColorSpace(dataspace), nullptr, releaseSurfaceProc, this); diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h index 509ac40f77..17e183a64e 100644 --- a/libs/renderengine/skia/AutoBackendTexture.h +++ b/libs/renderengine/skia/AutoBackendTexture.h @@ -95,14 +95,13 @@ public: // Makes a new SkImage from the texture content. // As SkImages are immutable but buffer content is not, we create // a new SkImage every time. - sk_sp makeImage(ui::Dataspace dataspace, SkAlphaType alphaType, - GrDirectContext* context) { - return mTexture->makeImage(dataspace, alphaType, context); + sk_sp makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) { + return mTexture->makeImage(dataspace, alphaType); } // Makes a new SkSurface from the texture content, if needed. - sk_sp getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context) { - return mTexture->getOrCreateSurface(dataspace, context); + sk_sp getOrCreateSurface(ui::Dataspace dataspace) { + return mTexture->getOrCreateSurface(dataspace); } SkColorType colorType() const { return mTexture->mColorType; } @@ -130,17 +129,17 @@ private: // Makes a new SkImage from the texture content. // As SkImages are immutable but buffer content is not, we create // a new SkImage every time. - sk_sp makeImage(ui::Dataspace dataspace, SkAlphaType alphaType, - GrDirectContext* context); + sk_sp makeImage(ui::Dataspace dataspace, SkAlphaType alphaType); // Makes a new SkSurface from the texture content, if needed. - sk_sp getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context); + sk_sp getOrCreateSurface(ui::Dataspace dataspace); GrBackendTexture mBackendTexture; GrAHardwareBufferUtils::DeleteImageProc mDeleteProc; GrAHardwareBufferUtils::UpdateImageProc mUpdateProc; GrAHardwareBufferUtils::TexImageCtx mImageCtx; + const GrDirectContext* mGrContext = nullptr; CleanupManager& mCleanupMgr; static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext); diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 6e393f03fa..aeedb0369e 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -679,8 +679,7 @@ void SkiaRenderEngine::drawLayersInternal( // wait on the buffer to be ready to use prior to using it waitFence(grContext, bufferFence); - sk_sp dstSurface = - surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext); + sk_sp dstSurface = surfaceTextureRef->getOrCreateSurface(display.outputDataspace); SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get()); if (dstCanvas == nullptr) { @@ -972,7 +971,7 @@ void SkiaRenderEngine::drawLayersInternal( : item.isOpaque ? kOpaque_SkAlphaType : item.usePremultipliedAlpha ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; - sk_sp image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext); + sk_sp image = imageTextureRef->makeImage(layerDataspace, alphaType); auto texMatrix = getSkM44(item.textureTransform).asM33(); // textureTansform was intended to be passed directly into a shader, so when -- GitLab From c289da6c3aa4022fd92004e263e7f2554fef01a9 Mon Sep 17 00:00:00 2001 From: Biswarup Pal Date: Wed, 13 Mar 2024 22:40:41 +0000 Subject: [PATCH 071/465] Revert^2 "Fix libtracing_perfetto performance impact" This reverts commit a3d2ec8a4c9b51494425d751870620e682d31eb8. Reason for revert: Fix missing null check Change-Id: Iafa241f158bbf59e1c81cb06ee587de7458750cf --- libs/tracing_perfetto/tracing_perfetto.cpp | 3 ++- .../tracing_perfetto/tracing_perfetto_internal.cpp | 14 +++++++++----- libs/tracing_perfetto/tracing_perfetto_internal.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index c7fb8bd9a8..19d1eb639e 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -131,7 +131,8 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } uint64_t getEnabledCategories() { - if (internal::isPerfettoSdkTracingEnabled()) { + if (internal::isPerfettoRegistered()) { + // TODO(b/303199244): Return only enabled categories and not all registered ones return internal::getDefaultCategories(); } else { return atrace_get_enabled_tags(); diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp index 58ba428610..758ace63ab 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp +++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp @@ -70,6 +70,8 @@ PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES); PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES); +std::atomic_bool is_perfetto_registered = false; + struct PerfettoTeCategory* toCategory(uint64_t inCategory) { switch (inCategory) { case TRACE_CATEGORY_ALWAYS: @@ -135,25 +137,26 @@ struct PerfettoTeCategory* toCategory(uint64_t inCategory) { } // namespace -bool isPerfettoSdkTracingEnabled() { - return android::os::perfetto_sdk_tracing(); +bool isPerfettoRegistered() { + return is_perfetto_registered; } struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { - if (!isPerfettoSdkTracingEnabled()) { + struct PerfettoTeCategory* perfettoCategory = toCategory(category); + if (perfettoCategory == nullptr) { return nullptr; } - struct PerfettoTeCategory* perfettoCategory = toCategory(category); bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED)); return enabled ? perfettoCategory : nullptr; } void registerWithPerfetto(bool test) { - if (!isPerfettoSdkTracingEnabled()) { + if (!android::os::perfetto_sdk_tracing()) { return; } + static std::once_flag registration; std::call_once(registration, [test]() { struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); @@ -161,6 +164,7 @@ void registerWithPerfetto(bool test) { PerfettoProducerInit(args); PerfettoTeInit(); PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES); + is_perfetto_registered = true; }); } diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h index 9a579f162a..79e4b8f1b4 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.h +++ b/libs/tracing_perfetto/tracing_perfetto_internal.h @@ -26,7 +26,7 @@ namespace tracing_perfetto { namespace internal { -bool isPerfettoSdkTracingEnabled(); +bool isPerfettoRegistered(); struct PerfettoTeCategory* toPerfettoCategory(uint64_t category); -- GitLab From cd6024fb88e4fef7843110f91108ae89789114e7 Mon Sep 17 00:00:00 2001 From: Zim Date: Thu, 14 Mar 2024 17:18:09 +0000 Subject: [PATCH 072/465] Fix check for whether a trace tag is enabled With the introduction of libperfetto_tracing, we need to check both perfetto track_events and atrace categories to determine if a trace tag is enabled. Perfetto SDK doesn't expose a list (or bitmask) of all enabled categories, but we can check if a specific category is enabled. Rewrote the internal impl for Trace#isTagEnabled to pass the tag into native and return a boolean instead of getting all the enabled tags from native and checking in Java. Test: atest libtracing_perfetto_tests Bug: 303199244 Change-Id: I175deb4d03fef486e4a2d571c8a8f1fbde220f36 --- libs/tracing_perfetto/include/tracing_perfetto.h | 2 +- libs/tracing_perfetto/tracing_perfetto.cpp | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h index 4e3c83fca3..2c1c2a49e7 100644 --- a/libs/tracing_perfetto/include/tracing_perfetto.h +++ b/libs/tracing_perfetto/include/tracing_perfetto.h @@ -46,7 +46,7 @@ Result traceInstantForTrack(uint64_t category, const char* trackName, Result traceCounter(uint64_t category, const char* name, int64_t value); -uint64_t getEnabledCategories(); +bool isTagEnabled(uint64_t category); } // namespace tracing_perfetto diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index 19d1eb639e..6f716eea9a 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -130,12 +130,13 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } } -uint64_t getEnabledCategories() { - if (internal::isPerfettoRegistered()) { - // TODO(b/303199244): Return only enabled categories and not all registered ones - return internal::getDefaultCategories(); +bool isTagEnabled(uint64_t category) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return true; } else { - return atrace_get_enabled_tags(); + return (atrace_get_enabled_tags() & category) != 0; } } -- GitLab From 566a61945bd727f64f6eeff7d9dd5d97624d28a4 Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Wed, 13 Mar 2024 20:45:34 +0000 Subject: [PATCH 073/465] Revert "Fix libtracing_perfetto performance impact" This reverts commit 81deab754e4728f98e09196528e0c3777ca70ed8. Reason for revert: Droid-monitored triggered revert due to likely culprit for breakages in b/329487228. Will be verifying through ABTD before submitting the revert. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:a3d2ec8a4c9b51494425d751870620e682d31eb8) Merged-In: I83f9eac29fb9f51e2d79053c6e98a047d7ab74d9 Change-Id: I83f9eac29fb9f51e2d79053c6e98a047d7ab74d9 --- libs/tracing_perfetto/tracing_perfetto.cpp | 3 +-- .../tracing_perfetto/tracing_perfetto_internal.cpp | 14 +++++++------- libs/tracing_perfetto/tracing_perfetto_internal.h | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index 19d1eb639e..c7fb8bd9a8 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -131,8 +131,7 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } uint64_t getEnabledCategories() { - if (internal::isPerfettoRegistered()) { - // TODO(b/303199244): Return only enabled categories and not all registered ones + if (internal::isPerfettoSdkTracingEnabled()) { return internal::getDefaultCategories(); } else { return atrace_get_enabled_tags(); diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp index 976db7e430..58ba428610 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.cpp +++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp @@ -70,8 +70,6 @@ PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES); PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES); -std::atomic_bool is_perfetto_registered = false; - struct PerfettoTeCategory* toCategory(uint64_t inCategory) { switch (inCategory) { case TRACE_CATEGORY_ALWAYS: @@ -137,11 +135,15 @@ struct PerfettoTeCategory* toCategory(uint64_t inCategory) { } // namespace -bool isPerfettoRegistered() { - return is_perfetto_registered; +bool isPerfettoSdkTracingEnabled() { + return android::os::perfetto_sdk_tracing(); } struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { + if (!isPerfettoSdkTracingEnabled()) { + return nullptr; + } + struct PerfettoTeCategory* perfettoCategory = toCategory(category); bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT( (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED)); @@ -149,10 +151,9 @@ struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) { } void registerWithPerfetto(bool test) { - if (!android::os::perfetto_sdk_tracing()) { + if (!isPerfettoSdkTracingEnabled()) { return; } - static std::once_flag registration; std::call_once(registration, [test]() { struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT(); @@ -160,7 +161,6 @@ void registerWithPerfetto(bool test) { PerfettoProducerInit(args); PerfettoTeInit(); PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES); - is_perfetto_registered = true; }); } diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h index 79e4b8f1b4..9a579f162a 100644 --- a/libs/tracing_perfetto/tracing_perfetto_internal.h +++ b/libs/tracing_perfetto/tracing_perfetto_internal.h @@ -26,7 +26,7 @@ namespace tracing_perfetto { namespace internal { -bool isPerfettoRegistered(); +bool isPerfettoSdkTracingEnabled(); struct PerfettoTeCategory* toPerfettoCategory(uint64_t category); -- GitLab From fc125ece97c0cc3025c7ca0ce08ffc00c24301cb Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Mon, 11 Mar 2024 20:08:27 -0400 Subject: [PATCH 074/465] Add and plumb abstraction layer over GrDirectContext Also changed GaussianBlurFilter's surface origin from kBottomLeft_GrSurfaceOrigin to kTopLeft_GrSurfaceOrigin. This doesn't seem to have an effect in practice, but aligns it with KawaseBlurFilter. Additionally, both blur filters now set the protected bit on the surfaces they create to reflect the protection status of the active context, as opposed to either the protection status of the input SkImage that is being blurred (Kawase) or always false (Gaussian). This should be equivalent behavior in the case of Kawase (and aligns with Graphite), and is likely a bug fix for Gaussian. Test: manual validation (GL+VK) & existing tests (refactor) Bug: b/293371537 Change-Id: I19b0258035ea5f319d04207ceb266f2cd1e87674 --- libs/renderengine/Android.bp | 1 + libs/renderengine/skia/AutoBackendTexture.cpp | 54 ++++----- libs/renderengine/skia/AutoBackendTexture.h | 11 +- libs/renderengine/skia/SkiaGLRenderEngine.cpp | 20 ++-- libs/renderengine/skia/SkiaGLRenderEngine.h | 6 +- libs/renderengine/skia/SkiaRenderEngine.cpp | 71 +++++------ libs/renderengine/skia/SkiaRenderEngine.h | 24 ++-- libs/renderengine/skia/SkiaVkRenderEngine.cpp | 28 +++-- libs/renderengine/skia/SkiaVkRenderEngine.h | 7 +- libs/renderengine/skia/VulkanInterface.cpp | 2 +- libs/renderengine/skia/VulkanInterface.h | 2 +- .../skia/compat/GaneshGpuContext.cpp | 111 ++++++++++++++++++ .../skia/compat/GaneshGpuContext.h | 51 ++++++++ .../renderengine/skia/compat/SkiaGpuContext.h | 76 ++++++++++++ libs/renderengine/skia/filters/BlurFilter.h | 7 +- .../skia/filters/GaussianBlurFilter.cpp | 9 +- .../skia/filters/GaussianBlurFilter.h | 3 +- .../skia/filters/KawaseBlurFilter.cpp | 10 +- .../skia/filters/KawaseBlurFilter.h | 2 +- 19 files changed, 361 insertions(+), 134 deletions(-) create mode 100644 libs/renderengine/skia/compat/GaneshGpuContext.cpp create mode 100644 libs/renderengine/skia/compat/GaneshGpuContext.h create mode 100644 libs/renderengine/skia/compat/SkiaGpuContext.h diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 09d7cb5d3f..39902b1635 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -88,6 +88,7 @@ filegroup { "skia/SkiaGLRenderEngine.cpp", "skia/SkiaVkRenderEngine.cpp", "skia/VulkanInterface.cpp", + "skia/compat/GaneshGpuContext.cpp", "skia/debug/CaptureTimer.cpp", "skia/debug/CommonPool.cpp", "skia/debug/SkiaCapture.cpp", diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index 26b280d520..02e7337053 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -21,12 +21,12 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS #include +#include #include #include #include #include #include -#include #include "ColorSpaces.h" #include "log/log_main.h" #include "utils/Trace.h" @@ -35,47 +35,37 @@ namespace android { namespace renderengine { namespace skia { -AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, +AutoBackendTexture::AutoBackendTexture(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, CleanupManager& cleanupMgr) - : mGrContext(context), mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) { + : mGrContext(context->grDirectContext()), + mCleanupMgr(cleanupMgr), + mIsOutputBuffer(isOutputBuffer) { ATRACE_CALL(); + AHardwareBuffer_Desc desc; AHardwareBuffer_describe(buffer, &desc); bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); GrBackendFormat backendFormat; - GrBackendApi backend = context->backend(); + GrBackendApi backend = mGrContext->backend(); if (backend == GrBackendApi::kOpenGL) { backendFormat = - GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false); + GrAHardwareBufferUtils::GetGLBackendFormat(mGrContext.get(), desc.format, false); mBackendTexture = - GrAHardwareBufferUtils::MakeGLBackendTexture(context, - buffer, - desc.width, - desc.height, - &mDeleteProc, - &mUpdateProc, - &mImageCtx, - createProtectedImage, - backendFormat, + GrAHardwareBufferUtils::MakeGLBackendTexture(mGrContext.get(), buffer, desc.width, + desc.height, &mDeleteProc, + &mUpdateProc, &mImageCtx, + createProtectedImage, backendFormat, isOutputBuffer); } else if (backend == GrBackendApi::kVulkan) { - backendFormat = - GrAHardwareBufferUtils::GetVulkanBackendFormat(context, - buffer, - desc.format, - false); + backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(mGrContext.get(), buffer, + desc.format, false); mBackendTexture = - GrAHardwareBufferUtils::MakeVulkanBackendTexture(context, - buffer, - desc.width, - desc.height, - &mDeleteProc, - &mUpdateProc, - &mImageCtx, - createProtectedImage, - backendFormat, - isOutputBuffer); + GrAHardwareBufferUtils::MakeVulkanBackendTexture(mGrContext.get(), buffer, + desc.width, desc.height, + &mDeleteProc, &mUpdateProc, + &mImageCtx, createProtectedImage, + backendFormat, isOutputBuffer); } else { LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast(backend)); } @@ -162,7 +152,7 @@ sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp ATRACE_CALL(); if (mBackendTexture.isValid()) { - mUpdateProc(mImageCtx, mGrContext); + mUpdateProc(mImageCtx, mGrContext.get()); } auto colorType = mColorType; @@ -173,7 +163,7 @@ sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp } sk_sp image = - SkImages::BorrowTextureFrom(mGrContext, mBackendTexture, kTopLeft_GrSurfaceOrigin, + SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, alphaType, toSkColorSpace(dataspace), releaseImageProc, this); if (image.get()) { @@ -194,7 +184,7 @@ sk_sp AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture"); if (!mSurface.get() || mDataspace != dataspace) { sk_sp surface = - SkSurfaces::WrapBackendTexture(mGrContext, mBackendTexture, + SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin, 0, mColorType, toSkColorSpace(dataspace), nullptr, releaseSurfaceProc, this); diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h index 17e183a64e..1d5b5655db 100644 --- a/libs/renderengine/skia/AutoBackendTexture.h +++ b/libs/renderengine/skia/AutoBackendTexture.h @@ -24,6 +24,7 @@ #include #include "android-base/macros.h" +#include "compat/SkiaGpuContext.h" #include #include @@ -80,7 +81,7 @@ public: // of shared ownership with Skia objects, so we wrap it here instead. class LocalRef { public: - LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, + LocalRef(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, CleanupManager& cleanupMgr) { mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr); mTexture->ref(); @@ -113,8 +114,10 @@ public: }; private: + DISALLOW_COPY_AND_ASSIGN(AutoBackendTexture); + // Creates a GrBackendTexture whose contents come from the provided buffer. - AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, + AutoBackendTexture(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, CleanupManager& cleanupMgr); // The only way to invoke dtor is with unref, when mUsageCount is 0. @@ -139,14 +142,14 @@ private: GrAHardwareBufferUtils::UpdateImageProc mUpdateProc; GrAHardwareBufferUtils::TexImageCtx mImageCtx; - const GrDirectContext* mGrContext = nullptr; + // TODO: b/293371537 - Graphite abstractions for ABT. + const sk_sp mGrContext = nullptr; CleanupManager& mCleanupMgr; static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext); static void releaseImageProc(SkImages::ReleaseContext releaseContext); int mUsageCount = 0; - const bool mIsOutputBuffer; sk_sp mImage = nullptr; sk_sp mSurface = nullptr; diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp index fea4129ec0..f10c98d6e2 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp @@ -21,6 +21,8 @@ #include "SkiaGLRenderEngine.h" +#include "compat/SkiaGpuContext.h" + #include #include #include @@ -207,7 +209,7 @@ std::unique_ptr SkiaGLRenderEngine::create( std::unique_ptr engine(new SkiaGLRenderEngine(args, display, ctxt, placeholder, protectedContext, protectedPlaceholder)); - engine->ensureGrContextsCreated(); + engine->ensureContextsCreated(); ALOGI("OpenGL ES informations:"); ALOGI("vendor : %s", extensions.getVendor()); @@ -295,9 +297,7 @@ SkiaGLRenderEngine::~SkiaGLRenderEngine() { eglReleaseThread(); } -SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts( - const GrContextOptions& options) { - +SkiaRenderEngine::Contexts SkiaGLRenderEngine::createContexts() { LOG_ALWAYS_FATAL_IF(isProtected(), "Cannot setup contexts while already in protected mode"); @@ -306,10 +306,10 @@ SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts( LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed"); SkiaRenderEngine::Contexts contexts; - contexts.first = GrDirectContexts::MakeGL(glInterface, options); + contexts.first = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor); if (supportsProtectedContentImpl()) { useProtectedContextImpl(GrProtected::kYes); - contexts.second = GrDirectContexts::MakeGL(glInterface, options); + contexts.second = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor); useProtectedContextImpl(GrProtected::kNo); } @@ -330,14 +330,14 @@ bool SkiaGLRenderEngine::useProtectedContextImpl(GrProtected isProtected) { return eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE; } -void SkiaGLRenderEngine::waitFence(GrDirectContext*, base::borrowed_fd fenceFd) { +void SkiaGLRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) { if (fenceFd.get() >= 0 && !waitGpuFence(fenceFd)) { ATRACE_NAME("SkiaGLRenderEngine::waitFence"); sync_wait(fenceFd.get(), -1); } } -base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) { +base::unique_fd SkiaGLRenderEngine::flushAndSubmit(SkiaGpuContext* context) { base::unique_fd drawFence = flush(); bool requireSync = drawFence.get() < 0; @@ -346,8 +346,8 @@ base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) { } else { ATRACE_BEGIN("Submit(sync=false)"); } - bool success = grContext->submit(requireSync ? GrSyncCpu::kYes : - GrSyncCpu::kNo); + bool success = + context->grDirectContext()->submit(requireSync ? GrSyncCpu::kYes : GrSyncCpu::kNo); ATRACE_END(); if (!success) { ALOGE("Failed to flush RenderEngine commands"); diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h index af3311041d..2e22478274 100644 --- a/libs/renderengine/skia/SkiaGLRenderEngine.h +++ b/libs/renderengine/skia/SkiaGLRenderEngine.h @@ -59,11 +59,11 @@ public: protected: // Implementations of abstract SkiaRenderEngine functions specific to // rendering backend - virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options); + virtual SkiaRenderEngine::Contexts createContexts(); bool supportsProtectedContentImpl() const override; bool useProtectedContextImpl(GrProtected isProtected) override; - void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override; - base::unique_fd flushAndSubmit(GrDirectContext* context) override; + void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; + base::unique_fd flushAndSubmit(SkiaGpuContext* context) override; void appendBackendSpecificInfoToDump(std::string& result) override; private: diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index aeedb0369e..27daeba092 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -74,6 +74,7 @@ #include "Cache.h" #include "ColorSpaces.h" +#include "compat/SkiaGpuContext.h" #include "filters/BlurFilter.h" #include "filters/GaussianBlurFilter.h" #include "filters/KawaseBlurFilter.h" @@ -290,14 +291,12 @@ void SkiaRenderEngine::finishRenderingAndAbandonContext() { delete mBlurFilter; } - if (mGrContext) { - mGrContext->flushAndSubmit(GrSyncCpu::kYes); - mGrContext->abandonContext(); + if (mContext) { + mContext->finishRenderingAndAbandonContext(); } - if (mProtectedGrContext) { - mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes); - mProtectedGrContext->abandonContext(); + if (mProtectedContext) { + mProtectedContext->finishRenderingAndAbandonContext(); } } @@ -308,24 +307,24 @@ void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) { } // release any scratch resources before switching into a new mode - if (getActiveGrContext()) { - getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly); + if (getActiveContext()) { + getActiveContext()->purgeUnlockedScratchResources(); } // Backend-specific way to switch to protected context if (useProtectedContextImpl( useProtectedContext ? GrProtected::kYes : GrProtected::kNo)) { mInProtectedContext = useProtectedContext; - // given that we are sharing the same thread between two GrContexts we need to + // given that we are sharing the same thread between two contexts we need to // make sure that the thread state is reset when switching between the two. - if (getActiveGrContext()) { - getActiveGrContext()->resetContext(); + if (getActiveContext()) { + getActiveContext()->resetContextIfApplicable(); } } } -GrDirectContext* SkiaRenderEngine::getActiveGrContext() { - return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get(); +SkiaGpuContext* SkiaRenderEngine::getActiveContext() { + return mInProtectedContext ? mProtectedContext.get() : mContext.get(); } static float toDegrees(uint32_t transform) { @@ -374,17 +373,12 @@ static bool needsToneMapping(ui::Dataspace sourceDataspace, ui::Dataspace destin sourceTransfer != destTransfer; } -void SkiaRenderEngine::ensureGrContextsCreated() { - if (mGrContext) { +void SkiaRenderEngine::ensureContextsCreated() { + if (mContext) { return; } - GrContextOptions options; - options.fDisableDriverCorrectnessWorkarounds = true; - options.fDisableDistanceFieldPaths = true; - options.fReducedShaderVariations = true; - options.fPersistentCache = &mSkSLCacheMonitor; - std::tie(mGrContext, mProtectedGrContext) = createDirectContexts(options); + std::tie(mContext, mProtectedContext) = createContexts(); } void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, @@ -413,7 +407,7 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, // switch back after the buffer is cached). However, for non-protected content we can bind // the texture in either GL context because they are initialized with the same share_context // which allows the texture state to be shared between them. - auto grContext = getActiveGrContext(); + auto context = getActiveContext(); auto& cache = mTextureCache; std::lock_guard lock(mRenderingMutex); @@ -424,8 +418,7 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER; } std::shared_ptr imageTextureRef = - std::make_shared(grContext, - buffer->toAHardwareBuffer(), + std::make_shared(context, buffer->toAHardwareBuffer(), isRenderable, mTextureCleanupMgr); cache.insert({buffer->getId(), imageTextureRef}); } @@ -477,7 +470,7 @@ std::shared_ptr SkiaRenderEngine::getOrCreateBacke return it->second; } } - return std::make_shared(getActiveGrContext(), + return std::make_shared(getActiveContext(), buffer->toAHardwareBuffer(), isOutputBuffer, mTextureCleanupMgr); } @@ -667,8 +660,8 @@ void SkiaRenderEngine::drawLayersInternal( validateOutputBufferUsage(buffer->getBuffer()); - auto grContext = getActiveGrContext(); - LOG_ALWAYS_FATAL_IF(grContext->abandoned(), "GrContext is abandoned/device lost at start of %s", + auto context = getActiveContext(); + LOG_ALWAYS_FATAL_IF(context->isAbandoned(), "Context is abandoned/device lost at start of %s", __func__); // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called @@ -677,7 +670,7 @@ void SkiaRenderEngine::drawLayersInternal( auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true); // wait on the buffer to be ready to use prior to using it - waitFence(grContext, bufferFence); + waitFence(context, bufferFence); sk_sp dstSurface = surfaceTextureRef->getOrCreateSurface(display.outputDataspace); @@ -844,7 +837,7 @@ void SkiaRenderEngine::drawLayersInternal( if (blurRect.width() > 0 && blurRect.height() > 0) { if (layer.backgroundBlurRadius > 0) { ATRACE_NAME("BackgroundBlur"); - auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius, + auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius, blurInput, blurRect); cachedBlurs[layer.backgroundBlurRadius] = blurredImage; @@ -858,7 +851,7 @@ void SkiaRenderEngine::drawLayersInternal( if (cachedBlurs[region.blurRadius] == nullptr) { ATRACE_NAME("BlurRegion"); cachedBlurs[region.blurRadius] = - mBlurFilter->generate(grContext, region.blurRadius, blurInput, + mBlurFilter->generate(context, region.blurRadius, blurInput, blurRect); } @@ -947,7 +940,7 @@ void SkiaRenderEngine::drawLayersInternal( // if the layer's buffer has a fence, then we must must respect the fence prior to using // the buffer. if (layer.source.buffer.fence != nullptr) { - waitFence(grContext, layer.source.buffer.fence->get()); + waitFence(context, layer.source.buffer.fence->get()); } // isOpaque means we need to ignore the alpha in the image, @@ -1158,7 +1151,7 @@ void SkiaRenderEngine::drawLayersInternal( skgpu::ganesh::Flush(activeSurface); } - auto drawFence = sp::make(flushAndSubmit(grContext)); + auto drawFence = sp::make(flushAndSubmit(context)); if (ATRACE_ENABLED()) { static gui::FenceMonitor sMonitor("RE Completion"); @@ -1168,11 +1161,11 @@ void SkiaRenderEngine::drawLayersInternal( } size_t SkiaRenderEngine::getMaxTextureSize() const { - return mGrContext->maxTextureSize(); + return mContext->getMaxTextureSize(); } size_t SkiaRenderEngine::getMaxViewportDims() const { - return mGrContext->maxRenderTargetSize(); + return mContext->getMaxRenderTargetSize(); } void SkiaRenderEngine::drawShadow(SkCanvas* canvas, @@ -1198,13 +1191,13 @@ void SkiaRenderEngine::onActiveDisplaySizeChanged(ui::Size size) { const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER; // start by resizing the current context - getActiveGrContext()->setResourceCacheLimit(maxResourceBytes); + getActiveContext()->setResourceCacheLimit(maxResourceBytes); // if it is possible to switch contexts then we will resize the other context const bool originalProtectedState = mInProtectedContext; useProtectedContext(!mInProtectedContext); if (mInProtectedContext != originalProtectedState) { - getActiveGrContext()->setResourceCacheLimit(maxResourceBytes); + getActiveContext()->setResourceCacheLimit(maxResourceBytes); // reset back to the initial context that was active when this method was called useProtectedContext(originalProtectedState); } @@ -1244,7 +1237,7 @@ void SkiaRenderEngine::dump(std::string& result) { {"skia", "Other"}, }; SkiaMemoryReporter gpuReporter(gpuResourceMap, true); - mGrContext->dumpMemoryStatistics(&gpuReporter); + mContext->dumpMemoryStatistics(&gpuReporter); StringAppendF(&result, "Skia's GPU Caches: "); gpuReporter.logTotals(result); gpuReporter.logOutput(result); @@ -1268,8 +1261,8 @@ void SkiaRenderEngine::dump(std::string& result) { StringAppendF(&result, "\n"); SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true); - if (mProtectedGrContext) { - mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter); + if (mProtectedContext) { + mProtectedContext->dumpMemoryStatistics(&gpuProtectedReporter); } StringAppendF(&result, "Skia's GPU Protected Caches: "); gpuProtectedReporter.logTotals(result); diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index e88d44cca6..9f892bd0f7 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -21,21 +21,21 @@ #include #include -#include #include #include #include #include #include +#include #include #include #include "AutoBackendTexture.h" #include "GrContextOptions.h" #include "SkImageInfo.h" -#include "SkiaRenderEngine.h" #include "android-base/macros.h" +#include "compat/SkiaGpuContext.h" #include "debug/SkiaCapture.h" #include "filters/BlurFilter.h" #include "filters/LinearEffect.h" @@ -76,24 +76,25 @@ public: bool supportsProtectedContent() const override { return supportsProtectedContentImpl(); } - void ensureGrContextsCreated(); + void ensureContextsCreated(); + protected: // This is so backends can stop the generic rendering state first before // cleaning up backend-specific state void finishRenderingAndAbandonContext(); // Functions that a given backend (GLES, Vulkan) must implement - using Contexts = std::pair, sk_sp>; - virtual Contexts createDirectContexts(const GrContextOptions& options) = 0; + using Contexts = std::pair, unique_ptr>; + virtual Contexts createContexts() = 0; virtual bool supportsProtectedContentImpl() const = 0; virtual bool useProtectedContextImpl(GrProtected isProtected) = 0; - virtual void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) = 0; - virtual base::unique_fd flushAndSubmit(GrDirectContext* context) = 0; + virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) = 0; + virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context) = 0; virtual void appendBackendSpecificInfoToDump(std::string& result) = 0; size_t getMaxTextureSize() const override final; size_t getMaxViewportDims() const override final; - GrDirectContext* getActiveGrContext(); + SkiaGpuContext* getActiveContext(); bool isProtected() const { return mInProtectedContext; } @@ -121,6 +122,8 @@ protected: int mTotalShadersCompiled = 0; }; + SkSLCacheMonitor mSkSLCacheMonitor; + private: void mapExternalTextureBuffer(const sp& buffer, bool isRenderable) override final; @@ -183,12 +186,11 @@ private: // Mutex guarding rendering operations, so that internal state related to // rendering that is potentially modified by multiple threads is guaranteed thread-safe. mutable std::mutex mRenderingMutex; - SkSLCacheMonitor mSkSLCacheMonitor; // Graphics context used for creating surfaces and submitting commands - sk_sp mGrContext; + unique_ptr mContext; // Same as above, but for protected content (eg. DRM) - sk_sp mProtectedGrContext; + unique_ptr mProtectedContext; bool mInProtectedContext = false; }; diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index f2f1b5d0ca..d854929960 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -21,12 +21,15 @@ #include "SkiaVkRenderEngine.h" +#include "compat/SkiaGpuContext.h" + #include #include -#include -#include +#include #include #include +#include +#include #include #include @@ -82,7 +85,7 @@ using base::StringAppendF; std::unique_ptr SkiaVkRenderEngine::create( const RenderEngineCreationArgs& args) { std::unique_ptr engine(new SkiaVkRenderEngine(args)); - engine->ensureGrContextsCreated(); + engine->ensureContextsCreated(); if (sVulkanInterface.isInitialized()) { ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__); @@ -103,16 +106,16 @@ SkiaVkRenderEngine::~SkiaVkRenderEngine() { finishRenderingAndAbandonContext(); } -SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts( - const GrContextOptions& options) { +SkiaRenderEngine::Contexts SkiaVkRenderEngine::createContexts() { sSetupVulkanInterface(); SkiaRenderEngine::Contexts contexts; - contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options); + contexts.first = SkiaGpuContext::MakeVulkan_Ganesh(sVulkanInterface.getGaneshBackendContext(), + mSkSLCacheMonitor); if (supportsProtectedContentImpl()) { - contexts.second = - GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(), - options); + contexts.second = SkiaGpuContext::MakeVulkan_Ganesh(sProtectedContentVulkanInterface + .getGaneshBackendContext(), + mSkSLCacheMonitor); } return contexts; @@ -139,7 +142,7 @@ static VulkanInterface& getVulkanInterface(bool protectedContext) { return sVulkanInterface; } -void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) { +void SkiaVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) { if (fenceFd.get() < 0) return; int dupedFd = dup(fenceFd.get()); @@ -153,10 +156,11 @@ void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd VkSemaphore waitSemaphore = getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release()); GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore); - grContext->wait(1, &beSemaphore, true /* delete after wait */); + context->grDirectContext()->wait(1, &beSemaphore, true /* delete after wait */); } -base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) { +base::unique_fd SkiaVkRenderEngine::flushAndSubmit(SkiaGpuContext* context) { + sk_sp grContext = context->grDirectContext(); VulkanInterface& vi = getVulkanInterface(isProtected()); VkSemaphore semaphore = vi.createExportableSemaphore(); diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h index ca0dcbf4ae..28b7595d41 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.h +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -21,6 +21,7 @@ #include "SkiaRenderEngine.h" #include "VulkanInterface.h" +#include "compat/SkiaGpuContext.h" namespace android { namespace renderengine { @@ -72,11 +73,11 @@ public: protected: // Implementations of abstract SkiaRenderEngine functions specific to // rendering backend - virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options); + SkiaRenderEngine::Contexts createContexts() override; bool supportsProtectedContentImpl() const override; bool useProtectedContextImpl(GrProtected isProtected) override; - void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override; - base::unique_fd flushAndSubmit(GrDirectContext* context) override; + void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; + base::unique_fd flushAndSubmit(SkiaGpuContext* context) override; void appendBackendSpecificInfoToDump(std::string& result) override; private: diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp index 453cdc1196..2f09a382d7 100644 --- a/libs/renderengine/skia/VulkanInterface.cpp +++ b/libs/renderengine/skia/VulkanInterface.cpp @@ -30,7 +30,7 @@ namespace android { namespace renderengine { namespace skia { -GrVkBackendContext VulkanInterface::getBackendContext() { +GrVkBackendContext VulkanInterface::getGaneshBackendContext() { GrVkBackendContext backendContext; backendContext.fInstance = mInstance; backendContext.fPhysicalDevice = mPhysicalDevice; diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h index c3936d9869..512e62c8f2 100644 --- a/libs/renderengine/skia/VulkanInterface.h +++ b/libs/renderengine/skia/VulkanInterface.h @@ -40,7 +40,7 @@ public: void teardown(); // TODO: b/293371537 - Graphite variant (external/skia/include/gpu/vk/VulkanBackendContext.h) - GrVkBackendContext getBackendContext(); + GrVkBackendContext getGaneshBackendContext(); VkSemaphore createExportableSemaphore(); VkSemaphore importSemaphoreFromSyncFd(int syncFd); int exportSemaphoreSyncFd(VkSemaphore semaphore); diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp new file mode 100644 index 0000000000..51c6a6cd1c --- /dev/null +++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2024 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. + */ + +#include "GaneshGpuContext.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace android::renderengine::skia { + +namespace { +// TODO: b/293371537 - Graphite variant. +static GrContextOptions ganeshOptions(GrContextOptions::PersistentCache& skSLCacheMonitor) { + GrContextOptions options; + options.fDisableDriverCorrectnessWorkarounds = true; + options.fDisableDistanceFieldPaths = true; + options.fReducedShaderVariations = true; + options.fPersistentCache = &skSLCacheMonitor; + return options; +} +} // namespace + +std::unique_ptr SkiaGpuContext::MakeGL_Ganesh( + sk_sp glInterface, + GrContextOptions::PersistentCache& skSLCacheMonitor) { + return std::make_unique( + GrDirectContexts::MakeGL(glInterface, ganeshOptions(skSLCacheMonitor))); +} + +std::unique_ptr SkiaGpuContext::MakeVulkan_Ganesh( + const GrVkBackendContext& grVkBackendContext, + GrContextOptions::PersistentCache& skSLCacheMonitor) { + return std::make_unique( + GrDirectContexts::MakeVulkan(grVkBackendContext, ganeshOptions(skSLCacheMonitor))); +} + +GaneshGpuContext::GaneshGpuContext(sk_sp grContext) : mGrContext(grContext) { + LOG_ALWAYS_FATAL_IF(mGrContext.get() == nullptr, "GrDirectContext creation failed"); +} + +sk_sp GaneshGpuContext::grDirectContext() { + return mGrContext; +} + +sk_sp GaneshGpuContext::createRenderTarget(SkImageInfo imageInfo) { + constexpr int kSampleCount = 1; // enable AA + constexpr SkSurfaceProps* kProps = nullptr; + constexpr bool kMipmapped = false; + return SkSurfaces::RenderTarget(mGrContext.get(), skgpu::Budgeted::kNo, imageInfo, kSampleCount, + kTopLeft_GrSurfaceOrigin, kProps, kMipmapped, + mGrContext->supportsProtectedContent()); +} + +size_t GaneshGpuContext::getMaxRenderTargetSize() const { + return mGrContext->maxRenderTargetSize(); +}; + +size_t GaneshGpuContext::getMaxTextureSize() const { + return mGrContext->maxTextureSize(); +}; + +bool GaneshGpuContext::isAbandoned() { + return mGrContext->abandoned(); +} + +void GaneshGpuContext::setResourceCacheLimit(size_t maxResourceBytes) { + mGrContext->setResourceCacheLimit(maxResourceBytes); +} + +void GaneshGpuContext::finishRenderingAndAbandonContext() { + mGrContext->flushAndSubmit(GrSyncCpu::kYes); + mGrContext->abandonContext(); +}; + +void GaneshGpuContext::purgeUnlockedScratchResources() { + mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly); +} + +void GaneshGpuContext::resetContextIfApplicable() { + mGrContext->resetContext(); // Only applicable to GL +}; + +void GaneshGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const { + mGrContext->dumpMemoryStatistics(traceMemoryDump); +} + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h new file mode 100644 index 0000000000..59001eccc2 --- /dev/null +++ b/libs/renderengine/skia/compat/GaneshGpuContext.h @@ -0,0 +1,51 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SkiaGpuContext.h" + +#include + +namespace android::renderengine::skia { + +class GaneshGpuContext : public SkiaGpuContext { +public: + GaneshGpuContext(sk_sp grContext); + ~GaneshGpuContext() override = default; + + sk_sp grDirectContext() override; + + sk_sp createRenderTarget(SkImageInfo imageInfo) override; + + size_t getMaxRenderTargetSize() const override; + size_t getMaxTextureSize() const override; + bool isAbandoned() override; + void setResourceCacheLimit(size_t maxResourceBytes) override; + + void finishRenderingAndAbandonContext() override; + void purgeUnlockedScratchResources() override; + void resetContextIfApplicable() override; + + void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override; + +private: + DISALLOW_COPY_AND_ASSIGN(GaneshGpuContext); + + const sk_sp mGrContext; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h new file mode 100644 index 0000000000..ba167f352d --- /dev/null +++ b/libs/renderengine/skia/compat/SkiaGpuContext.h @@ -0,0 +1,76 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#undef LOG_TAG +#define LOG_TAG "RenderEngine" + +#include +#include +#include +#include + +#include + +namespace android::renderengine::skia { + +/** + * Abstraction over Ganesh and Graphite's underlying context-like objects. + */ +class SkiaGpuContext { +public: + static std::unique_ptr MakeGL_Ganesh( + sk_sp glInterface, + GrContextOptions::PersistentCache& skSLCacheMonitor); + + // TODO: b/293371537 - Graphite variant. + static std::unique_ptr MakeVulkan_Ganesh( + const GrVkBackendContext& grVkBackendContext, + GrContextOptions::PersistentCache& skSLCacheMonitor); + + virtual ~SkiaGpuContext() = default; + + // TODO: b/293371537 - Maybe expose whether this SkiaGpuContext is using Ganesh or Graphite? + /** + * Only callable on Ganesh-backed instances of SkiaGpuContext, otherwise fatal. + */ + virtual sk_sp grDirectContext() { + LOG_ALWAYS_FATAL("grDirectContext() called on a non-Ganesh instance of SkiaGpuContext!"); + } + + /** + * Notes: + * - The surface doesn't count against Skia's caching budgets. + * - Protected status is set to match the implementation's underlying context. + * - The origin of the surface in texture space corresponds to the top-left content pixel. + * - AA is always enabled. + */ + virtual sk_sp createRenderTarget(SkImageInfo imageInfo) = 0; + + virtual bool isAbandoned() = 0; + virtual size_t getMaxRenderTargetSize() const = 0; + virtual size_t getMaxTextureSize() const = 0; + virtual void setResourceCacheLimit(size_t maxResourceBytes) = 0; + + virtual void finishRenderingAndAbandonContext() = 0; + virtual void purgeUnlockedScratchResources() = 0; + virtual void resetContextIfApplicable() = 0; // No-op outside of GL (&& Ganesh at this point.) + + virtual void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const = 0; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h index 9cddc757fc..180c92262f 100644 --- a/libs/renderengine/skia/filters/BlurFilter.h +++ b/libs/renderengine/skia/filters/BlurFilter.h @@ -21,6 +21,8 @@ #include #include +#include "../compat/SkiaGpuContext.h" + using namespace std; namespace android { @@ -38,8 +40,9 @@ public: virtual ~BlurFilter(){} // Execute blur, saving it to a texture - virtual sk_sp generate(GrRecordingContext* context, const uint32_t radius, - const sk_sp blurInput, const SkRect& blurRect) const = 0; + virtual sk_sp generate(SkiaGpuContext* context, const uint32_t radius, + const sk_sp blurInput, + const SkRect& blurRect) const = 0; /** * Draw the blurred content (from the generate method) into the canvas. diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp index e72c501336..c9499cbc24 100644 --- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp +++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp @@ -42,14 +42,13 @@ static const float BLUR_SIGMA_SCALE = 0.57735f; GaussianBlurFilter::GaussianBlurFilter(): BlurFilter(/* maxCrossFadeRadius= */ 0.0f) {} -sk_sp GaussianBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius, - const sk_sp input, const SkRect& blurRect) - const { +sk_sp GaussianBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius, + const sk_sp input, + const SkRect& blurRect) const { // Create blur surface with the bit depth and colorspace of the original surface SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale), std::ceil(blurRect.height() * kInputScale)); - sk_sp surface = SkSurfaces::RenderTarget(context, - skgpu::Budgeted::kNo, scaledInfo); + sk_sp surface = context->createRenderTarget(scaledInfo); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.h b/libs/renderengine/skia/filters/GaussianBlurFilter.h index a4febd2257..878ab21b36 100644 --- a/libs/renderengine/skia/filters/GaussianBlurFilter.h +++ b/libs/renderengine/skia/filters/GaussianBlurFilter.h @@ -37,9 +37,8 @@ public: virtual ~GaussianBlurFilter(){} // Execute blur, saving it to a texture - sk_sp generate(GrRecordingContext* context, const uint32_t radius, + sk_sp generate(SkiaGpuContext* context, const uint32_t radius, const sk_sp blurInput, const SkRect& blurRect) const override; - }; } // namespace skia diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp index 09f09a697a..7a070d7024 100644 --- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp +++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp @@ -73,8 +73,7 @@ static sk_sp makeImage(SkSurface* surface, SkRuntimeShaderBuilder* buil return surface->makeImageSnapshot(); } -sk_sp KawaseBlurFilter::generate(GrRecordingContext* context, - const uint32_t blurRadius, +sk_sp KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius, const sk_sp input, const SkRect& blurRect) const { LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__); @@ -108,12 +107,7 @@ sk_sp KawaseBlurFilter::generate(GrRecordingContext* context, input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix); blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale; - constexpr int kSampleCount = 1; - constexpr bool kMipmapped = false; - constexpr SkSurfaceProps* kProps = nullptr; - sk_sp surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kNo, scaledInfo, - kSampleCount, kTopLeft_GrSurfaceOrigin, - kProps, kMipmapped, input->isProtected()); + sk_sp surface = context->createRenderTarget(scaledInfo); LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__); sk_sp tmpBlur = makeImage(surface.get(), &blurBuilder); diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.h b/libs/renderengine/skia/filters/KawaseBlurFilter.h index 0ac5ac8866..429a5378a3 100644 --- a/libs/renderengine/skia/filters/KawaseBlurFilter.h +++ b/libs/renderengine/skia/filters/KawaseBlurFilter.h @@ -42,7 +42,7 @@ public: virtual ~KawaseBlurFilter(){} // Execute blur, saving it to a texture - sk_sp generate(GrRecordingContext* context, const uint32_t radius, + sk_sp generate(SkiaGpuContext* context, const uint32_t radius, const sk_sp blurInput, const SkRect& blurRect) const override; private: -- GitLab From fed621c61161890fe7c21c7204f6fe2a957bb013 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Thu, 14 Mar 2024 15:46:05 +0000 Subject: [PATCH 075/465] Deprecate ASurfaceTransactionStats_getAcquireTime Test: Builds Bug: 203080148 Change-Id: I586339b82e408e0ac90944ab280a2d5b79309141 --- include/android/surface_control.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/android/surface_control.h b/include/android/surface_control.h index 082387e63a..fb1419f0b2 100644 --- a/include/android/surface_control.h +++ b/include/android/surface_control.h @@ -236,6 +236,10 @@ void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** surface_ * it is acquired. If no acquire_fence_fd was provided, this timestamp will be set to -1. * * Available since API level 29. + * + * @deprecated This may return SIGNAL_PENDING because the stats can arrive before the acquire + * fence has signaled, depending on internal timing differences. Therefore the caller should + * use the acquire fence passed in to setBuffer and query the signal time. */ int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* surface_transaction_stats, ASurfaceControl* surface_control) -- GitLab From b95d4aa41b0c6a5219da941a94dab2073ce02be5 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 11 Mar 2024 22:48:04 +0000 Subject: [PATCH 076/465] InputTracer: Disallow adding new targets from a derived event tracker There are complex interactions between features in InputDispatcher that can lead to cancelations being generated for a derived event. When this happens, calling dispatchToTargetHint will result in a crash. Since it is difficult to determine exactly how to reproduce this issue in a test case, loosen the check so that we only crash if we are trying to add a new target, which should never happen. Bug: 328928072 Bug: 210460522 Test: will re-enable the flag and wait for new crash reports Change-Id: I0134a12b40e1da33a94d61b721e967dfdbc95fe8 --- services/inputflinger/dispatcher/trace/InputTracer.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 49e6e21828..4cf6a89db6 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -101,14 +101,15 @@ std::unique_ptr InputTracer::createTrackerForSyntheticEve void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, const InputTarget& target) { - if (isDerivedCookie(cookie)) { - LOG(FATAL) << "Event target cannot be updated from a derived cookie."; - } auto& eventState = getState(cookie); if (eventState->isEventProcessingComplete) { // TODO(b/210460522): Disallow adding new targets after eventProcessingComplete() is called. return; } + if (isDerivedCookie(cookie)) { + // TODO(b/210460522): Disallow adding new targets from a derived cookie. + return; + } // TODO(b/210460522): Determine if the event is sensitive based on the target. } -- GitLab From 8c3b143e1349ed443cc2cb660e45ebd666b5ffec Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 9 Feb 2024 23:34:16 +0000 Subject: [PATCH 077/465] InputTracer: Introduce TracedEventArgs to track secure events Introduce TracedEventArgs to give the tracing backend additional information about the traced event. We start by tracking whether an event is secure. An event is marked as secure if it is targeting at least one secure window. For now, do not trace secure events in the perfetto backend. Bug: 210460522 Test: manual with perfetto Change-Id: I41648d769319e47486556d0a2d6b8f02142048d2 --- .../dispatcher/trace/InputTracer.cpp | 25 +++++++++++------ .../dispatcher/trace/InputTracer.h | 4 +-- .../trace/InputTracingBackendInterface.h | 12 ++++++--- .../trace/InputTracingPerfettoBackend.cpp | 27 ++++++++++++++----- .../trace/InputTracingPerfettoBackend.h | 6 ++--- .../dispatcher/trace/ThreadedBackend.cpp | 25 ++++++++++------- .../dispatcher/trace/ThreadedBackend.h | 10 ++++--- .../tests/FakeInputTracingBackend.cpp | 9 ++++--- .../tests/FakeInputTracingBackend.h | 8 +++--- 9 files changed, 83 insertions(+), 43 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 4cf6a89db6..47e27beff7 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -59,9 +59,10 @@ TracedEvent createTracedEvent(const KeyEntry& e) { e.downTime, e.flags, e.repeatCount}; } -void writeEventToBackend(const TracedEvent& event, InputTracingBackendInterface& backend) { - std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e); }, - [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e); }}, +void writeEventToBackend(const TracedEvent& event, const TracedEventArgs args, + InputTracingBackendInterface& backend) { + std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, args); }, + [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, args); }}, event); } @@ -110,7 +111,11 @@ void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, // TODO(b/210460522): Disallow adding new targets from a derived cookie. return; } - // TODO(b/210460522): Determine if the event is sensitive based on the target. + if (target.windowHandle != nullptr) { + eventState->isSecure |= target.windowHandle->getInfo()->layoutParamsFlags.test( + gui::WindowInfo::Flag::SECURE); + // TODO(b/210460522): Set events as sensitive when the IME connection is active. + } } void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { @@ -145,7 +150,8 @@ std::unique_ptr InputTracer::traceDerivedEvent( // It is possible for a derived event to be dispatched some time after the original event // is dispatched, such as in the case of key fallback events. To account for these cases, // derived events can be traced after the processing is complete for the original event. - writeEventToBackend(eventState->events.back(), *mBackend); + const TracedEventArgs traceArgs{.isSecure = eventState->isSecure}; + writeEventToBackend(eventState->events.back(), traceArgs, *mBackend); } return std::make_unique(std::move(eventState), /*isDerived=*/true); } @@ -184,6 +190,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, const int32_t windowId = dispatchEntry.windowId.value_or(0); const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0; + // TODO(b/210460522): Pass HMAC into traceEventDispatch. const WindowDispatchArgs windowDispatchArgs{std::move(traced), dispatchEntry.deliveryTime, dispatchEntry.resolvedFlags, @@ -195,7 +202,8 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}, resolvedKeyRepeatCount}; if (eventState->isEventProcessingComplete) { - mBackend->traceWindowDispatch(std::move(windowDispatchArgs)); + mBackend->traceWindowDispatch(std::move(windowDispatchArgs), + TracedEventArgs{.isSecure = eventState->isSecure}); } else { eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs)); } @@ -214,12 +222,13 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { void InputTracer::EventState::onEventProcessingComplete() { // Write all of the events known so far to the trace. + const TracedEventArgs traceArgs{.isSecure = isSecure}; for (const auto& event : events) { - writeEventToBackend(event, *tracer.mBackend); + writeEventToBackend(event, traceArgs, *tracer.mBackend); } // Write all pending dispatch args to the trace. for (const auto& windowDispatchArgs : pendingDispatchArgs) { - tracer.mBackend->traceWindowDispatch(windowDispatchArgs); + tracer.mBackend->traceWindowDispatch(windowDispatchArgs, traceArgs); } pendingDispatchArgs.clear(); diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 8da9632d7c..458f4aadc9 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -66,8 +66,8 @@ private: bool isEventProcessingComplete{false}; // A queue to hold dispatch args from being traced until event processing is complete. std::vector pendingDispatchArgs; - // TODO(b/210460522): Add additional args for tracking event sensitivity and - // dispatch target UIDs. + // True if the event is targeting at least one secure window; + bool isSecure{false}; }; // Get the event state associated with a tracking cookie. diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 94a86b94a2..865e8277c0 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -74,6 +74,12 @@ struct TracedMotionEvent { /** A representation of a traced input event. */ using TracedEvent = std::variant; +/** Additional information about an input event being traced. */ +struct TracedEventArgs { + // True if the event is targeting at least one secure window. + bool isSecure; +}; + /** * An interface for the tracing backend, used for setting a custom backend for testing. */ @@ -82,10 +88,10 @@ public: virtual ~InputTracingBackendInterface() = default; /** Trace a KeyEvent. */ - virtual void traceKeyEvent(const TracedKeyEvent&) = 0; + virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) = 0; /** Trace a MotionEvent. */ - virtual void traceMotionEvent(const TracedMotionEvent&) = 0; + virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) = 0; /** Trace an event being sent to a window. */ struct WindowDispatchArgs { @@ -100,7 +106,7 @@ public: std::array hmac; int32_t resolvedKeyRepeatCount; }; - virtual void traceWindowDispatch(const WindowDispatchArgs&) = 0; + virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) = 0; }; } // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 46ad9e16d9..8ef9ca504d 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -63,7 +63,12 @@ PerfettoBackend::PerfettoBackend() { }); } -void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) { +void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, + const TracedEventArgs& args) { + if (args.isSecure) { + // For now, avoid tracing secure event entirely. + return; + } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); @@ -72,7 +77,11 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) { }); } -void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) { +void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) { + if (args.isSecure) { + // For now, avoid tracing secure event entirely. + return; + } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); @@ -81,13 +90,17 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) { }); } -void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) { +void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs, + const TracedEventArgs& args) { + if (args.isSecure) { + // For now, avoid tracing secure event entirely. + return; + } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto tracePacket = ctx.NewTracePacket(); - auto* inputEventProto = tracePacket->set_android_input_event(); - auto* dispatchEventProto = inputEventProto->set_dispatcher_window_dispatch_event(); - AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, - *dispatchEventProto); + auto* inputEvent = tracePacket->set_android_input_event(); + auto* dispatchEvent = inputEvent->set_dispatcher_window_dispatch_event(); + AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent); }); } diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index fefcfb3ae0..d4553758a4 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -48,9 +48,9 @@ public: PerfettoBackend(); ~PerfettoBackend() override = default; - void traceKeyEvent(const TracedKeyEvent&) override; - void traceMotionEvent(const TracedMotionEvent&) override; - void traceWindowDispatch(const WindowDispatchArgs&) override; + void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override; + void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override; + void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override; class InputEventDataSource : public perfetto::DataSource { public: diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp index 25bc2276c9..b1791b3268 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp @@ -53,23 +53,26 @@ ThreadedBackend::~ThreadedBackend() { } template -void ThreadedBackend::traceMotionEvent(const TracedMotionEvent& event) { +void ThreadedBackend::traceMotionEvent(const TracedMotionEvent& event, + const TracedEventArgs& traceArgs) { std::scoped_lock lock(mLock); - mQueue.emplace_back(event); + mQueue.emplace_back(event, traceArgs); mThreadWakeCondition.notify_all(); } template -void ThreadedBackend::traceKeyEvent(const TracedKeyEvent& event) { +void ThreadedBackend::traceKeyEvent(const TracedKeyEvent& event, + const TracedEventArgs& traceArgs) { std::scoped_lock lock(mLock); - mQueue.emplace_back(event); + mQueue.emplace_back(event, traceArgs); mThreadWakeCondition.notify_all(); } template -void ThreadedBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) { +void ThreadedBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs, + const TracedEventArgs& traceArgs) { std::scoped_lock lock(mLock); - mQueue.emplace_back(dispatchArgs); + mQueue.emplace_back(dispatchArgs, traceArgs); mThreadWakeCondition.notify_all(); } @@ -93,11 +96,13 @@ void ThreadedBackend::threadLoop() { // Trace the events into the backend without holding the lock to reduce the amount of // work performed in the critical section. - for (const auto& entry : entries) { - std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend.traceMotionEvent(e); }, - [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e); }, + for (const auto& [entry, traceArgs] : entries) { + std::visit(Visitor{[&](const TracedMotionEvent& e) { + mBackend.traceMotionEvent(e, traceArgs); + }, + [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e, traceArgs); }, [&](const WindowDispatchArgs& args) { - mBackend.traceWindowDispatch(args); + mBackend.traceWindowDispatch(args, traceArgs); }}, entry); } diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h index 5776cf9417..5d4efbb7e9 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h @@ -38,9 +38,9 @@ public: ThreadedBackend(Backend&& innerBackend); ~ThreadedBackend() override; - void traceKeyEvent(const TracedKeyEvent&) override; - void traceMotionEvent(const TracedMotionEvent&) override; - void traceWindowDispatch(const WindowDispatchArgs&) override; + void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override; + void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override; + void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override; private: std::mutex mLock; @@ -48,7 +48,9 @@ private: bool mThreadExit GUARDED_BY(mLock){false}; std::condition_variable mThreadWakeCondition; Backend mBackend; - using TraceEntry = std::variant; + using TraceEntry = + std::pair, + TracedEventArgs>; std::vector mQueue GUARDED_BY(mLock); using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs; diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp index 08738e3973..dc74f84d2c 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.cpp +++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp @@ -163,7 +163,8 @@ base::Result VerifyingTrace::verifyEventTraced(const Event& expectedEvent, // --- FakeInputTracingBackend --- -void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) { +void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event, + const trace::TracedEventArgs&) { { std::scoped_lock lock(mTrace->mLock); mTrace->mTracedEvents.emplace(event.id, event); @@ -171,7 +172,8 @@ void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) mTrace->mEventTracedCondition.notify_all(); } -void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event) { +void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event, + const trace::TracedEventArgs&) { { std::scoped_lock lock(mTrace->mLock); mTrace->mTracedEvents.emplace(event.id, event); @@ -179,7 +181,8 @@ void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& e mTrace->mEventTracedCondition.notify_all(); } -void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args) { +void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args, + const trace::TracedEventArgs&) { { std::scoped_lock lock(mTrace->mLock); mTrace->mTracedWindowDispatches.push_back(args); diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h index 1b3613d5c5..b149ffe7d8 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.h +++ b/services/inputflinger/tests/FakeInputTracingBackend.h @@ -83,9 +83,11 @@ public: private: std::shared_ptr mTrace; - void traceKeyEvent(const trace::TracedKeyEvent& entry) override; - void traceMotionEvent(const trace::TracedMotionEvent& entry) override; - void traceWindowDispatch(const WindowDispatchArgs& entry) override; + void traceKeyEvent(const trace::TracedKeyEvent& entry, const trace::TracedEventArgs&) override; + void traceMotionEvent(const trace::TracedMotionEvent& entry, + const trace::TracedEventArgs&) override; + void traceWindowDispatch(const WindowDispatchArgs& entry, + const trace::TracedEventArgs&) override; }; } // namespace android::inputdispatcher -- GitLab From 4b408beb74c6d9f561e36d4c416d2a94a46af29d Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 21 Feb 2024 19:19:47 +0000 Subject: [PATCH 078/465] InputTracer: Don't make WindowDispatchArgs an inner class For consistency with other structs and definitions in the backend interface, move WindowDispatchArgs outside of the interface class. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I9fe98448bace259faa40b082981211fdf9b1e221 --- .../trace/AndroidInputEventProtoConverter.cpp | 3 +-- .../trace/AndroidInputEventProtoConverter.h | 2 +- .../dispatcher/trace/InputTracer.h | 2 -- .../trace/InputTracingBackendInterface.h | 26 ++++++++++--------- .../dispatcher/trace/ThreadedBackend.h | 2 -- .../tests/FakeInputTracingBackend.cpp | 14 +++++----- .../tests/FakeInputTracingBackend.h | 5 ++-- 7 files changed, 24 insertions(+), 30 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp index a61fa85d6e..cee741cfc7 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp @@ -72,8 +72,7 @@ void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& even } void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent( - const InputTracingBackendInterface::WindowDispatchArgs& args, - proto::AndroidWindowInputDispatchEvent& outProto) { + const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto) { std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry); outProto.set_vsync_id(args.vsyncId); outProto.set_window_id(args.windowId); diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h index 8a46f1518b..ab5f9ca610 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h @@ -32,7 +32,7 @@ public: static void toProtoMotionEvent(const TracedMotionEvent& event, proto::AndroidMotionEvent& outProto); static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto); - static void toProtoWindowDispatchEvent(const InputTracingBackendInterface::WindowDispatchArgs&, + static void toProtoWindowDispatchEvent(const WindowDispatchArgs&, proto::AndroidWindowInputDispatchEvent& outProto); }; diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 458f4aadc9..4ef6ca61ae 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -52,8 +52,6 @@ public: private: std::unique_ptr mBackend; - using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs; - // The state of a tracked event, shared across all events derived from the original event. struct EventState { explicit inline EventState(InputTracer& tracer) : tracer(tracer){}; diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 865e8277c0..6eef12e536 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -80,6 +80,20 @@ struct TracedEventArgs { bool isSecure; }; +/** Additional information about an input event being dispatched to a window. */ +struct WindowDispatchArgs { + TracedEvent eventEntry; + nsecs_t deliveryTime; + int32_t resolvedFlags; + gui::Uid targetUid; + int64_t vsyncId; + int32_t windowId; + ui::Transform transform; + ui::Transform rawTransform; + std::array hmac; + int32_t resolvedKeyRepeatCount; +}; + /** * An interface for the tracing backend, used for setting a custom backend for testing. */ @@ -94,18 +108,6 @@ public: virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) = 0; /** Trace an event being sent to a window. */ - struct WindowDispatchArgs { - TracedEvent eventEntry; - nsecs_t deliveryTime; - int32_t resolvedFlags; - gui::Uid targetUid; - int64_t vsyncId; - int32_t windowId; - ui::Transform transform; - ui::Transform rawTransform; - std::array hmac; - int32_t resolvedKeyRepeatCount; - }; virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) = 0; }; diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h index 5d4efbb7e9..cab47af1c1 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h @@ -53,8 +53,6 @@ private: TracedEventArgs>; std::vector mQueue GUARDED_BY(mLock); - using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs; - void threadLoop(); }; diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp index dc74f84d2c..069b50d051 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.cpp +++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp @@ -37,10 +37,9 @@ inline auto getId(const trace::TracedEvent& v) { return std::visit([](const auto& event) { return event.id; }, v); } -MotionEvent toInputEvent( - const trace::TracedMotionEvent& e, - const trace::InputTracingBackendInterface::WindowDispatchArgs& dispatchArgs, - const std::array& hmac) { +MotionEvent toInputEvent(const trace::TracedMotionEvent& e, + const trace::WindowDispatchArgs& dispatchArgs, + const std::array& hmac) { MotionEvent traced; traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action, e.actionButton, dispatchArgs.resolvedFlags, e.edgeFlags, e.metaState, e.buttonState, @@ -51,8 +50,7 @@ MotionEvent toInputEvent( return traced; } -KeyEvent toInputEvent(const trace::TracedKeyEvent& e, - const trace::InputTracingBackendInterface::WindowDispatchArgs& dispatchArgs, +KeyEvent toInputEvent(const trace::TracedKeyEvent& e, const trace::WindowDispatchArgs& dispatchArgs, const std::array& hmac) { KeyEvent traced; traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action, @@ -120,7 +118,7 @@ base::Result VerifyingTrace::verifyEventTraced(const Event& expectedEvent, auto tracedDispatchesIt = std::find_if(mTracedWindowDispatches.begin(), mTracedWindowDispatches.end(), - [&](const WindowDispatchArgs& args) { + [&](const trace::WindowDispatchArgs& args) { return args.windowId == expectedWindowId && getId(args.eventEntry) == expectedEvent.getId(); }); @@ -181,7 +179,7 @@ void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& e mTrace->mEventTracedCondition.notify_all(); } -void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args, +void FakeInputTracingBackend::traceWindowDispatch(const trace::WindowDispatchArgs& args, const trace::TracedEventArgs&) { { std::scoped_lock lock(mTrace->mLock); diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h index b149ffe7d8..ab05d6b532 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.h +++ b/services/inputflinger/tests/FakeInputTracingBackend.h @@ -59,8 +59,7 @@ private: std::mutex mLock; std::condition_variable mEventTracedCondition; std::unordered_map mTracedEvents GUARDED_BY(mLock); - using WindowDispatchArgs = trace::InputTracingBackendInterface::WindowDispatchArgs; - std::vector mTracedWindowDispatches GUARDED_BY(mLock); + std::vector mTracedWindowDispatches GUARDED_BY(mLock); std::vector, int32_t /*windowId*/>> mExpectedEvents GUARDED_BY(mLock); @@ -86,7 +85,7 @@ private: void traceKeyEvent(const trace::TracedKeyEvent& entry, const trace::TracedEventArgs&) override; void traceMotionEvent(const trace::TracedMotionEvent& entry, const trace::TracedEventArgs&) override; - void traceWindowDispatch(const WindowDispatchArgs& entry, + void traceWindowDispatch(const trace::WindowDispatchArgs& entry, const trace::TracedEventArgs&) override; }; -- GitLab From 6af5b8c429e1d6d843eb85c41d2104695250ca19 Mon Sep 17 00:00:00 2001 From: Matt Buckley Date: Mon, 5 Feb 2024 23:54:19 +0000 Subject: [PATCH 079/465] Update HintManager to use NDK backend - Update HintManager and its aidl types to use NDK backend - Switch HintManager to using PowerHAL's WorkDuration - Update SDK WorkDuration.java to no longer be parcelable, since it isn't being sent over binders anymore. - Remove duplicate WorkDuration in powermanager in favor of PowerHAL's Bug: 315894228 Test: atest HintManagerServiceTest Test: atest PerformanceHintNativeTestCases Test: atest PerformanceHintManagerTest Change-Id: I03494b76a5a3c0132c672de5406a6b190fbffcd4 --- services/powermanager/Android.bp | 5 +- services/powermanager/WorkDuration.cpp | 52 -------------- .../include/android/WorkDuration.h | 71 ------------------- 3 files changed, 4 insertions(+), 124 deletions(-) delete mode 100644 services/powermanager/WorkDuration.cpp delete mode 100644 services/powermanager/include/android/WorkDuration.h diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp index 3ea08febfd..4f65e77462 100644 --- a/services/powermanager/Android.bp +++ b/services/powermanager/Android.bp @@ -20,7 +20,6 @@ cc_library_shared { "PowerHintSessionWrapper.cpp", "PowerSaveState.cpp", "Temperature.cpp", - "WorkDuration.cpp", "WorkSource.cpp", ":libpowermanager_aidl", ], @@ -52,6 +51,10 @@ cc_library_shared { "android.hardware.power@1.3", ], + whole_static_libs: [ + "android.os.hintmanager_aidl-ndk", + ], + cflags: [ "-Wall", "-Werror", diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp deleted file mode 100644 index bd2b10a149..0000000000 --- a/services/powermanager/WorkDuration.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "WorkDuration" - -#include -#include -#include -#include - -namespace android::os { - -WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos, - int64_t cpuDurationNanos, int64_t gpuDurationNanos) - : timestampNanos(0), - actualTotalDurationNanos(totalDurationNanos), - workPeriodStartTimestampNanos(startTimestampNanos), - actualCpuDurationNanos(cpuDurationNanos), - actualGpuDurationNanos(gpuDurationNanos) {} - -status_t WorkDuration::writeToParcel(Parcel* parcel) const { - if (parcel == nullptr) { - ALOGE("%s: Null parcel", __func__); - return BAD_VALUE; - } - - parcel->writeInt64(workPeriodStartTimestampNanos); - parcel->writeInt64(actualTotalDurationNanos); - parcel->writeInt64(actualCpuDurationNanos); - parcel->writeInt64(actualGpuDurationNanos); - parcel->writeInt64(timestampNanos); - return OK; -} - -status_t WorkDuration::readFromParcel(const Parcel*) { - return INVALID_OPERATION; -} - -} // namespace android::os diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h deleted file mode 100644 index 26a575f834..0000000000 --- a/services/powermanager/include/android/WorkDuration.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -struct AWorkDuration {}; - -namespace android::os { - -/** - * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in - * binder calls. - * This file needs to be kept in sync with the WorkDuration in - * frameworks/base/core/java/android/os/WorkDuration.java - */ -struct WorkDuration : AWorkDuration, android::Parcelable { - WorkDuration() = default; - ~WorkDuration() = default; - - WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos, - int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos); - status_t writeToParcel(Parcel* parcel) const override; - status_t readFromParcel(const Parcel* parcel) override; - - inline bool equalsWithoutTimestamp(const WorkDuration& other) const { - return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos && - actualTotalDurationNanos == other.actualTotalDurationNanos && - actualCpuDurationNanos == other.actualCpuDurationNanos && - actualGpuDurationNanos == other.actualGpuDurationNanos; - } - - bool operator==(const WorkDuration& other) const { - return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other); - } - - bool operator!=(const WorkDuration& other) const { return !(*this == other); } - - friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) { - os << "{" - << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos - << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos - << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos - << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos - << ", timestampNanos: " << workDuration.timestampNanos << "}"; - return os; - } - - int64_t timestampNanos; - int64_t actualTotalDurationNanos; - int64_t workPeriodStartTimestampNanos; - int64_t actualCpuDurationNanos; - int64_t actualGpuDurationNanos; -}; - -} // namespace android::os -- GitLab From 19f01d0e17ab896e3a5ab1f90608175ffca95718 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Wed, 13 Mar 2024 20:42:24 -0700 Subject: [PATCH 080/465] NoPreference frame rate category is no-op Change NoPreference frame rate category to behave like a no-op. When all layers are NoPreference votes, the current frame rate should be used, instead of choosing Min. This also allows idle timer to trigger. Bug: 328084518 Test: atest libsurfaceflinger_unittest Test: Manual sysui jank suit Change-Id: Ibe25ab8427367c5b86a1eaf0ed43434b6d270691 --- .../Scheduler/RefreshRateSelector.cpp | 18 ++- .../unittests/RefreshRateSelectorTest.cpp | 143 ++++++++++++++++-- 2 files changed, 147 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index ad59f1a574..56c29e2574 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -506,6 +506,8 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector 0; const DisplayModeId activeModeId = activeMode.getId(); @@ -643,6 +652,7 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vectorgetPeakFps()) + << "Did not get expected frame rate for" + << " category=" << ftl::enum_string(testCase.frameRateCategory); + EXPECT_EQ(testCase.expectedModeId, + selector.getBestFrameRateMode(layers, signals).modePtr->getId()) + << "Did not get expected DisplayModeId for modeId=" + << ftl::to_underlying(testCase.expectedModeId) + << " category=" << ftl::enum_string(testCase.frameRateCategory); + } + }; + + { + // IdleTimer not configured + auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120); + ASSERT_EQ(0ms, selector.getIdleTimerTimeout()); + + runTest(selector, + std::initializer_list{ + // Rate does not change due to NoPreference. + {.frameRateCategory = FrameRateCategory::NoPreference, + .expectedFrameRate = 120_Hz, + .expectedModeId = kModeId120}, + {.voteType = LayerVoteType::NoVote, + .expectedFrameRate = 120_Hz, + .expectedModeId = kModeId120}, + {.frameRateCategory = FrameRateCategory::NoPreference, + .expectedFrameRate = 120_Hz, + .expectedModeId = kModeId120}, + }, + {.idle = false}); + } + + // IdleTimer configured + constexpr std::chrono::milliseconds kIdleTimerTimeoutMs = 10ms; + auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120, + Config{ + .idleTimerTimeout = kIdleTimerTimeoutMs, + }); + ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction()); + ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout()); + runTest(selector, + std::initializer_list{ + // Rate won't change immediately and will stay 120 due to NoPreference, as + // idle timer did not timeout yet. + {.frameRateCategory = FrameRateCategory::NoPreference, + .expectedFrameRate = 120_Hz, + .expectedModeId = kModeId120}, + {.voteType = LayerVoteType::NoVote, + .expectedFrameRate = 120_Hz, + .expectedModeId = kModeId120}, + {.frameRateCategory = FrameRateCategory::NoPreference, + .expectedFrameRate = 120_Hz, + .expectedModeId = kModeId120}, + }, + {.idle = false}); + + // Idle timer is triggered using GlobalSignals. + ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction()); + ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout()); + runTest(selector, + std::initializer_list{ + {.frameRateCategory = FrameRateCategory::NoPreference, + .expectedFrameRate = 60_Hz, + .expectedModeId = kModeId60}, + {.voteType = LayerVoteType::NoVote, + .expectedFrameRate = 60_Hz, + .expectedModeId = kModeId60}, + {.frameRateCategory = FrameRateCategory::NoPreference, + .expectedFrameRate = 60_Hz, + .expectedModeId = kModeId60}, + }, + {.idle = true}); +} + TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) { if (GetParam() != Config::FrameRateOverride::Enabled) { @@ -1992,8 +2116,7 @@ TEST_P(RefreshRateSelectorTest, const std::initializer_list testCases = { // These layers may switch modes because smoothSwitchOnly=false. {FrameRateCategory::Default, false, 120_Hz, kModeId120}, - // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate. - {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60}, + {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120}, {FrameRateCategory::Low, false, 30_Hz, kModeId60}, {FrameRateCategory::Normal, false, 60_Hz, kModeId60}, {FrameRateCategory::High, false, 120_Hz, kModeId120}, -- GitLab From 45ed7a82f535f1f627088a23f71f39f7aabbea85 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 14 Mar 2024 17:36:22 -0700 Subject: [PATCH 081/465] SF: VsyncTimeline was added twice This CL fixes the issue where VsyncTimeline was added twice. Fixes: 329310308 Test: presubmit Change-Id: I8cc574f3f2fe6f7e68149108cc3bcf507e798a37 --- .../Scheduler/VSyncPredictor.cpp | 11 +++++++--- .../tests/unittests/VSyncPredictorTest.cpp | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index db1930da54..acb37603c1 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -377,15 +377,20 @@ void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) { mRenderRateOpt = renderRate; const auto renderPeriodDelta = prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0; - const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() && - mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs(); if (applyImmediately) { + ATRACE_FORMAT_INSTANT("applyImmediately"); while (mTimelines.size() > 1) { mTimelines.pop_front(); } mTimelines.front().setRenderRate(renderRate); - } else if (newRenderRateIsHigher) { + return; + } + + const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() && + mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs(); + if (newRenderRateIsHigher) { + ATRACE_FORMAT_INSTANT("newRenderRateIsHigher"); mTimelines.clear(); mLastCommittedVsync = TimePoint::fromNs(0); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index 48707cb6d2..aac1cacf97 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -944,6 +944,27 @@ TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) { EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000)); } +// b/329310308 +TEST_F(VSyncPredictorTest, renderRateChangeAfterAppliedImmediately) { + tracker.addVsyncTimestamp(1000); + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(2000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(2001), Eq(3000)); + + tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000)); + + tracker.setRenderRate(Fps::fromPeriodNsecs(4000), /*applyImmediately*/ false); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(9000)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(13000)); +} + } // namespace android::scheduler // TODO(b/129481165): remove the #pragma below and fix conversion issues -- GitLab From c0ed405f3f23972fa58f3af1e5e76593b651c7f1 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 5 Mar 2024 00:31:36 +0000 Subject: [PATCH 082/465] PointerChoreographer: Remove ability to create mouse controllers OTF In the following CL, we made a change that results in the mouse cursor position being valid whenever there is a mouse or touchpad connected: I55898a3de1beb0f83f5da199521f26a886fb596c This means we are no longer depending on creating mouse controllers on-the-fly based on the input events. Remove the logic that creates mouse controllers on-the-fly. Bug: 327717240 Test: atest inputflinger_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:990d8713a9913136dba9eb57f3b351edaa00be26) Merged-In: I0fa10ef48055d80136083a1c0ab23522f6683fdc Change-Id: I0fa10ef48055d80136083a1c0ab23522f6683fdc --- services/inputflinger/PointerChoreographer.cpp | 18 +++++++----------- services/inputflinger/PointerChoreographer.h | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 3ac4285304..3e7c1c71ef 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -104,7 +104,7 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio << args.dump(); } - auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId); + auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X); const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y); @@ -124,7 +124,7 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio } NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) { - auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId); + auto [displayId, pc] = ensureMouseControllerLocked(args.displayId); NotifyMotionArgs newArgs(args); newArgs.displayId = displayId; @@ -308,17 +308,13 @@ int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisp return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId; } -std::pair -PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) { +std::pair PointerChoreographer::ensureMouseControllerLocked( + int32_t associatedDisplayId) { const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId); - // Get the mouse pointer controller for the display, or create one if it doesn't exist. - auto [it, emplaced] = - mMousePointersByDisplay.try_emplace(displayId, - getMouseControllerConstructor(displayId)); - if (emplaced) { - notifyPointerDisplayIdChangedLocked(); - } + auto it = mMousePointersByDisplay.find(displayId); + LOG_ALWAYS_FATAL_IF(it == mMousePointersByDisplay.end(), + "There is no mouse controller created for display %d", displayId); return {displayId, *it->second}; } diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index 6aab3aade0..f46038ef73 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -113,7 +113,7 @@ private: void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock); const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); - std::pair getDisplayIdAndMouseControllerLocked( + std::pair ensureMouseControllerLocked( int32_t associatedDisplayId) REQUIRES(mLock); InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock); bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock); -- GitLab From eb83011e1db18c15af48e143b4d85b2f8a8f19cb Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 5 Mar 2024 03:54:00 +0000 Subject: [PATCH 083/465] PointerChoreographer: Do not call the policy with the lock held Since there is already precedent for InputReader to interact with the policy with its lock held, we must not do the same from other components, since it can result in deadlocks. In this CL, we ensure that the PointerChoreographer does not interact with the policy while holding its lock. The exception is the use of the factory method for PointerController, which is only part of the policy to work around dependency issues with graphics libraries. Bug: 327717240 Test: atest inputflinger_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5a51a2280743605a51e2f9d632077e9297276520) Merged-In: Ib81d72898a212275d95f9d84d89a16e7172e108e Change-Id: Ib81d72898a212275d95f9d84d89a16e7172e108e --- .../inputflinger/PointerChoreographer.cpp | 121 ++++++++++++------ services/inputflinger/PointerChoreographer.h | 6 +- .../PointerChoreographerPolicyInterface.h | 6 + 3 files changed, 91 insertions(+), 42 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 3e7c1c71ef..9db3574389 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -26,6 +26,7 @@ namespace android { namespace { + bool isFromMouse(const NotifyMotionArgs& args) { return isFromSource(args.source, AINPUT_SOURCE_MOUSE) && args.pointerProperties[0].toolType == ToolType::MOUSE; @@ -44,13 +45,23 @@ bool isHoverAction(int32_t action) { bool isStylusHoverEvent(const NotifyMotionArgs& args) { return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action); } + +inline void notifyPointerDisplayChange(std::optional> change, + PointerChoreographerPolicyInterface& policy) { + if (!change) { + return; + } + const auto& [displayId, cursorPosition] = *change; + policy.notifyPointerDisplayIdChanged(displayId, cursorPosition); +} + } // namespace // --- PointerChoreographer --- PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, PointerChoreographerPolicyInterface& policy) - : mTouchControllerConstructor([this]() REQUIRES(mLock) { + : mTouchControllerConstructor([this]() { return mPolicy.createPointerController( PointerControllerInterface::ControllerType::TOUCH); }), @@ -62,10 +73,16 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, mStylusPointerIconEnabled(false) {} void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { - std::scoped_lock _l(mLock); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + + mInputDeviceInfos = args.inputDeviceInfos; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock - mInputDeviceInfos = args.inputDeviceInfos; - updatePointerControllersLocked(); + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); mNextListener.notify(args); } @@ -329,7 +346,7 @@ bool PointerChoreographer::canUnfadeOnDisplay(int32_t displayId) { return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end(); } -void PointerChoreographer::updatePointerControllersLocked() { +PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() { std::set mouseDisplaysToKeep; std::set touchDevicesToKeep; std::set stylusDevicesToKeep; @@ -378,11 +395,12 @@ void PointerChoreographer::updatePointerControllersLocked() { mInputDeviceInfos.end(); }); - // Notify the policy if there's a change on the pointer display ID. - notifyPointerDisplayIdChangedLocked(); + // Check if we need to notify the policy if there's a change on the pointer display ID. + return calculatePointerDisplayChangeToNotify(); } -void PointerChoreographer::notifyPointerDisplayIdChangedLocked() { +PointerChoreographer::PointerDisplayChange +PointerChoreographer::calculatePointerDisplayChangeToNotify() { int32_t displayIdToNotify = ADISPLAY_ID_NONE; FloatPoint cursorPosition = {0, 0}; if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId); @@ -394,38 +412,49 @@ void PointerChoreographer::notifyPointerDisplayIdChangedLocked() { displayIdToNotify = pointerController->getDisplayId(); cursorPosition = pointerController->getPosition(); } - if (mNotifiedPointerDisplayId == displayIdToNotify) { - return; + return {}; } - mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition); mNotifiedPointerDisplayId = displayIdToNotify; + return {{displayIdToNotify, cursorPosition}}; } void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) { - std::scoped_lock _l(mLock); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); - mDefaultMouseDisplayId = displayId; - updatePointerControllersLocked(); + mDefaultMouseDisplayId = displayId; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } void PointerChoreographer::setDisplayViewports(const std::vector& viewports) { - std::scoped_lock _l(mLock); - for (const auto& viewport : viewports) { - const int32_t displayId = viewport.displayId; - if (const auto it = mMousePointersByDisplay.find(displayId); - it != mMousePointersByDisplay.end()) { - it->second->setDisplayViewport(viewport); - } - for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) { - const InputDeviceInfo* info = findInputDeviceLocked(deviceId); - if (info && info->getAssociatedDisplayId() == displayId) { - stylusPointerController->setDisplayViewport(viewport); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + for (const auto& viewport : viewports) { + const int32_t displayId = viewport.displayId; + if (const auto it = mMousePointersByDisplay.find(displayId); + it != mMousePointersByDisplay.end()) { + it->second->setDisplayViewport(viewport); + } + for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) { + const InputDeviceInfo* info = findInputDeviceLocked(deviceId); + if (info && info->getAssociatedDisplayId() == displayId) { + stylusPointerController->setDisplayViewport(viewport); + } } } - } - mViewports = viewports; - notifyPointerDisplayIdChangedLocked(); + mViewports = viewports; + pointerDisplayChange = calculatePointerDisplayChangeToNotify(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } std::optional PointerChoreographer::getViewportForPointerDevice( @@ -449,21 +478,33 @@ FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) { } void PointerChoreographer::setShowTouchesEnabled(bool enabled) { - std::scoped_lock _l(mLock); - if (mShowTouchesEnabled == enabled) { - return; - } - mShowTouchesEnabled = enabled; - updatePointerControllersLocked(); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + if (mShowTouchesEnabled == enabled) { + return; + } + mShowTouchesEnabled = enabled; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) { - std::scoped_lock _l(mLock); - if (mStylusPointerIconEnabled == enabled) { - return; - } - mStylusPointerIconEnabled = enabled; - updatePointerControllersLocked(); + PointerDisplayChange pointerDisplayChange; + + { // acquire lock + std::scoped_lock _l(mLock); + if (mStylusPointerIconEnabled == enabled) { + return; + } + mStylusPointerIconEnabled = enabled; + pointerDisplayChange = updatePointerControllersLocked(); + } // release lock + + notifyPointerDisplayChange(pointerDisplayChange, mPolicy); } bool PointerChoreographer::setPointerIcon( diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index f46038ef73..db1488b546 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -109,8 +109,10 @@ public: void dump(std::string& dump) override; private: - void updatePointerControllersLocked() REQUIRES(mLock); - void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock); + using PointerDisplayChange = + std::optional>; + [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock); + [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock); const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); std::pair ensureMouseControllerLocked( diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h index 8b47b555e5..462aedc539 100644 --- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h +++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h @@ -25,6 +25,9 @@ namespace android { * * This is the interface that PointerChoreographer uses to talk to Window Manager and other * system components. + * + * NOTE: In general, the PointerChoreographer must not interact with the policy while + * holding any locks. */ class PointerChoreographerPolicyInterface { public: @@ -37,6 +40,9 @@ public: * for and runnable on the host, the PointerController implementation must be in a separate * library, libinputservice, that has the additional dependencies. The PointerController * will be mocked when testing PointerChoreographer. + * + * Since this is a factory method used to work around dependencies, it will not interact with + * other input components and may be called with the PointerChoreographer lock held. */ virtual std::shared_ptr createPointerController( PointerControllerInterface::ControllerType type) = 0; -- GitLab From 2b920275d0b1cbce0d83aa8a87ad82db121ffbc0 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 27 Feb 2024 19:49:51 -0800 Subject: [PATCH 084/465] Add InputConsumerNoResampling, which is a rewrite of InputConsumer In this CL, an InputConsumer with built-in looper handling is being added. This will be useful for native code that needs batching, and that provides choreographer callbacks directly into native code. Before this CL (and with this CL, temporarily) the InputConsumer logic was split between native and jni layers. In general, we shouldn't have logic inside jni. But in this case, the situation was also making the code difficult to reason and debug. In this new InputConsumerNoResampling class, all of the features of InputConsumer and NativeInputReceiver are combined, except for resampling. That will be done separately, at a later time. As a result, we will not be switching to the new InputConsumerNoResampling class right away. Once resampling is added, we can switch to the new InputConsumerNoResampling (and rename it to InputConsumer), and delete the old InputConsumer. In the meantime, the new InputConsumerNoResampling will be useful in the new NDK APIs. There, having resampling is not critical. Bug: 311142655 Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_repeat=100 --gtest_break_on_failure Change-Id: I468ddbd8406c4bf9f5e022f79fd1a582ba680633 --- include/input/InputConsumerNoResampling.h | 211 +++++ libs/input/Android.bp | 1 + libs/input/InputConsumerNoResampling.cpp | 534 ++++++++++++ libs/input/tests/Android.bp | 1 + ...tPublisherAndConsumerNoResampling_test.cpp | 807 ++++++++++++++++++ 5 files changed, 1554 insertions(+) create mode 100644 include/input/InputConsumerNoResampling.h create mode 100644 libs/input/InputConsumerNoResampling.cpp create mode 100644 libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h new file mode 100644 index 0000000000..7e6ae9b107 --- /dev/null +++ b/include/input/InputConsumerNoResampling.h @@ -0,0 +1,211 @@ +/** + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include "InputTransport.h" + +namespace android { + +/** + * An interface to receive batched input events. Even if you don't want batching, you still have to + * use this interface, and some of the events will be batched if your implementation is slow to + * handle the incoming input. + */ +class InputConsumerCallbacks { +public: + virtual ~InputConsumerCallbacks(){}; + virtual void onKeyEvent(KeyEvent&& event, uint32_t seq) = 0; + virtual void onMotionEvent(MotionEvent&& event, uint32_t seq) = 0; + /** + * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents". + * If you don't want batching, then call "consumeBatchedInputEvents" immediately with + * std::nullopt frameTime to receive the pending motion event(s). + * @param pendingBatchSource the source of the pending batch. + */ + virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0; + virtual void onFocusEvent(FocusEvent&& event, uint32_t seq) = 0; + virtual void onCaptureEvent(CaptureEvent&& event, uint32_t seq) = 0; + virtual void onDragEvent(DragEvent&& event, uint32_t seq) = 0; + virtual void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) = 0; +}; + +/** + * Consumes input events from an input channel. + * + * This is a re-implementation of InputConsumer that does not have resampling at the current moment. + * A lot of the higher-level logic has been folded into this class, to make it easier to use. + * In the legacy class, InputConsumer, the consumption logic was partially handled in the jni layer, + * as well as various actions like adding the fd to the Choreographer. + * + * TODO(b/297226446): use this instead of "InputConsumer": + * - Add resampling to this class + * - Allow various resampling strategies to be specified + * - Delete the old "InputConsumer" and use this class instead, renaming it to "InputConsumer". + * - Add tracing + * - Update all tests to use the new InputConsumer + * + * This class is not thread-safe. We are currently allowing the constructor to run on any thread, + * but all of the remaining APIs should be invoked on the looper thread only. + */ +class InputConsumerNoResampling final { +public: + explicit InputConsumerNoResampling(const std::shared_ptr& channel, + sp looper, InputConsumerCallbacks& callbacks); + ~InputConsumerNoResampling(); + + /** + * Must be called exactly once for each event received through the callbacks. + */ + void finishInputEvent(uint32_t seq, bool handled); + void reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime); + /** + * If you want to consume all events immediately (disable batching), the you still must call + * this. For frameTime, use a std::nullopt. + * @param frameTime the time up to which consume the events. When there's double (or triple) + * buffering, you may want to not consume all events currently available, because you could be + * still working on an older frame, but there could already have been events that arrived that + * are more recent. + * @return whether any events were actually consumed + */ + bool consumeBatchedInputEvents(std::optional frameTime); + /** + * Returns true when there is *likely* a pending batch or a pending event in the channel. + * + * This is only a performance hint and may return false negative results. Clients should not + * rely on availability of the message based on the return value. + */ + bool probablyHasInput() const; + + std::string getName() { return mChannel->getName(); } + + std::string dump() const; + +private: + std::shared_ptr mChannel; + sp mLooper; + InputConsumerCallbacks& mCallbacks; + + // Looper-related infrastructure + /** + * This class is needed to associate the function "handleReceiveCallback" with the provided + * looper. The callback sent to the looper is RefBase - based, so we can't just send a reference + * of this class directly to the looper. + */ + class LooperEventCallback : public LooperCallback { + public: + LooperEventCallback(std::function callback) : mCallback(callback) {} + int handleEvent(int /*fd*/, int events, void* /*data*/) override { + return mCallback(events); + } + + private: + std::function mCallback; + }; + sp mCallback; + /** + * The actual code that executes when the looper encounters available data on the InputChannel. + */ + int handleReceiveCallback(int events); + int mFdEvents; + void setFdEvents(int events); + + void ensureCalledOnLooperThread(const char* func) const; + + // Event-reading infrastructure + /** + * A fifo queue of events to be sent to the InputChannel. We can't send all InputMessages to + * the channel immediately when they are produced, because it's possible that the InputChannel + * is blocked (if the channel buffer is full). When that happens, we don't want to drop the + * events. Therefore, events should only be erased from the queue after they've been + * successfully written to the InputChannel. + */ + std::queue mOutboundQueue; + /** + * Try to send all of the events in mOutboundQueue over the InputChannel. Not all events might + * actually get sent, because it's possible that the channel is blocked. + */ + void processOutboundEvents(); + + /** + * The time at which each event with the sequence number 'seq' was consumed. + * This data is provided in 'finishInputEvent' so that the receiving end can measure the latency + * This collection is populated when the event is received, and the entries are erased when the + * events are finished. It should not grow infinitely because if an event is not ack'd, ANR + * will be raised for that connection, and no further events will be posted to that channel. + */ + std::unordered_map mConsumeTimes; + /** + * Find and return the consumeTime associated with the provided sequence number. Crashes if + * the provided seq number is not found. + */ + nsecs_t popConsumeTime(uint32_t seq); + + // Event reading and processing + /** + * Read all of the available events from the InputChannel + */ + std::vector readAllMessages(); + + /** + * Send InputMessage to the corresponding InputConsumerCallbacks function. + * @param msg + */ + void handleMessage(const InputMessage& msg) const; + + // Batching + /** + * Batch messages that can be batched. When an unbatchable message is encountered, send it + * to the InputConsumerCallbacks immediately. If there are batches remaining, + * notify InputConsumerCallbacks. + */ + void handleMessages(std::vector&& messages); + /** + * Batched InputMessages, per deviceId. + * For each device, we are storing a queue of batched messages. These will all be collapsed into + * a single MotionEvent (up to a specific frameTime) when the consumer calls + * `consumeBatchedInputEvents`. + */ + std::map> mBatches; + /** + * A map from a single sequence number to several sequence numbers. This is needed because of + * batching. When batching is enabled, a single MotionEvent will contain several samples. Each + * sample came from an individual InputMessage of Type::Motion, and therefore will have to be + * finished individually. Therefore, when the app calls "finish" on a (possibly batched) + * MotionEvent, we will need to check this map in case there are multiple sequence numbers + * associated with a single number that the app provided. + * + * For example: + * Suppose we received 4 InputMessage's of type Motion, with action MOVE: + * InputMessage(MOVE) InputMessage(MOVE) InputMessage(MOVE) InputMessage(MOVE) + * seq=10 seq=11 seq=12 seq=13 + * The app consumed them all as a batch, which means that the app received a single MotionEvent + * with historySize=3 and seq = 10. + * + * This map will look like: + * { + * 10: [11, 12, 13], + * } + * So the sequence number 10 will have 3 other sequence numbers associated with it. + * When the app calls 'finish' for seq=10, we need to call 'finish' 4 times total, for sequence + * numbers 10, 11, 12, 13. The app is not aware of the sequence numbers of each sample inside + * the batched MotionEvent that it received. + */ + std::map> mBatchedSequenceNumbers; +}; + +} // namespace android diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 65e93a9a87..3278c23b6f 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -205,6 +205,7 @@ cc_library { "AccelerationCurve.cpp", "Input.cpp", "InputConsumer.cpp", + "InputConsumerNoResampling.cpp", "InputDevice.cpp", "InputEventLabels.cpp", "InputTransport.cpp", diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp new file mode 100644 index 0000000000..1462c9059a --- /dev/null +++ b/libs/input/InputConsumerNoResampling.cpp @@ -0,0 +1,534 @@ +/** + * Copyright 2024 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. + */ + +#define LOG_TAG "InputTransport" +#define ATRACE_TAG ATRACE_TAG_INPUT + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace input_flags = com::android::input::flags; + +namespace android { + +namespace { + +/** + * Log debug messages relating to the consumer end of the transport channel. + * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart) + */ +const bool DEBUG_TRANSPORT_CONSUMER = + __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO); + +void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) { + event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, + msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, + msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, + msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, + msg.body.key.eventTime); +} + +void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) { + event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus); +} + +void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) { + event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled); +} + +void initializeDragEvent(DragEvent& event, const InputMessage& msg) { + event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y, + msg.body.drag.isExiting); +} + +void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { + const uint32_t pointerCount = msg.body.motion.pointerCount; + std::vector pointerProperties; + pointerProperties.reserve(pointerCount); + std::vector pointerCoords; + pointerCoords.reserve(pointerCount); + for (uint32_t i = 0; i < pointerCount; i++) { + pointerProperties.push_back(msg.body.motion.pointers[i].properties); + pointerCoords.push_back(msg.body.motion.pointers[i].coords); + } + + ui::Transform transform; + transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx, + msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1}); + ui::Transform displayTransform; + displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw, + msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, + 0, 0, 1}); + event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, + msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, + msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, + msg.body.motion.metaState, msg.body.motion.buttonState, + msg.body.motion.classification, transform, msg.body.motion.xPrecision, + msg.body.motion.yPrecision, msg.body.motion.xCursorPosition, + msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime, + msg.body.motion.eventTime, pointerCount, pointerProperties.data(), + pointerCoords.data()); +} + +void addSample(MotionEvent& event, const InputMessage& msg) { + uint32_t pointerCount = msg.body.motion.pointerCount; + std::vector pointerCoords; + pointerCoords.reserve(pointerCount); + for (uint32_t i = 0; i < pointerCount; i++) { + pointerCoords.push_back(msg.body.motion.pointers[i].coords); + } + + // TODO(b/329770983): figure out if it's safe to combine events with mismatching metaState + event.setMetaState(event.getMetaState() | msg.body.motion.metaState); + event.addSample(msg.body.motion.eventTime, pointerCoords.data()); +} + +void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) { + event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); +} + +std::string outboundMessageToString(const InputMessage& outboundMsg) { + switch (outboundMsg.header.type) { + case InputMessage::Type::FINISHED: { + return android::base::StringPrintf(" Finish: seq=%" PRIu32 " handled=%s", + outboundMsg.header.seq, + toString(outboundMsg.body.finished.handled)); + } + case InputMessage::Type::TIMELINE: { + return android::base:: + StringPrintf(" Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64 + ", presentTime=%" PRId64, + outboundMsg.body.timeline.eventId, + outboundMsg.body.timeline + .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME], + outboundMsg.body.timeline + .graphicsTimeline[GraphicsTimeline::PRESENT_TIME]); + } + default: { + LOG(FATAL) << "Outbound message must be FINISHED or TIMELINE, got " + << ftl::enum_string(outboundMsg.header.type); + return "Unreachable"; + } + } +} + +InputMessage createFinishedMessage(uint32_t seq, bool handled, nsecs_t consumeTime) { + InputMessage msg; + msg.header.type = InputMessage::Type::FINISHED; + msg.header.seq = seq; + msg.body.finished.handled = handled; + msg.body.finished.consumeTime = consumeTime; + return msg; +} + +InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTime, + nsecs_t presentTime) { + InputMessage msg; + msg.header.type = InputMessage::Type::TIMELINE; + msg.header.seq = 0; + msg.body.timeline.eventId = inputEventId; + msg.body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime; + msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime; + return msg; +} + +} // namespace + +using android::base::Result; +using android::base::StringPrintf; + +// --- InputConsumerNoResampling --- + +InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr& channel, + sp looper, + InputConsumerCallbacks& callbacks) + : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) { + LOG_ALWAYS_FATAL_IF(mLooper == nullptr); + mCallback = sp::make( + std::bind(&InputConsumerNoResampling::handleReceiveCallback, this, + std::placeholders::_1)); + // In the beginning, there are no pending outbounds events; we only care about receiving + // incoming data. + setFdEvents(ALOOPER_EVENT_INPUT); +} + +InputConsumerNoResampling::~InputConsumerNoResampling() { + ensureCalledOnLooperThread(__func__); + consumeBatchedInputEvents(std::nullopt); + while (!mOutboundQueue.empty()) { + processOutboundEvents(); + // This is our last chance to ack the events. If we don't ack them here, we will get an ANR, + // so keep trying to send the events as long as they are present in the queue. + } + setFdEvents(0); +} + +int InputConsumerNoResampling::handleReceiveCallback(int events) { + // Allowed return values of this function as documented in LooperCallback::handleEvent + constexpr int REMOVE_CALLBACK = 0; + constexpr int KEEP_CALLBACK = 1; + + if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) { + // This error typically occurs when the publisher has closed the input channel + // as part of removing a window or finishing an IME session, in which case + // the consumer will soon be disposed as well. + if (DEBUG_TRANSPORT_CONSUMER) { + LOG(INFO) << "The channel was hung up or an error occurred: " << mChannel->getName(); + } + return REMOVE_CALLBACK; + } + + int handledEvents = 0; + if (events & ALOOPER_EVENT_INPUT) { + std::vector messages = readAllMessages(); + handleMessages(std::move(messages)); + handledEvents |= ALOOPER_EVENT_INPUT; + } + + if (events & ALOOPER_EVENT_OUTPUT) { + processOutboundEvents(); + handledEvents |= ALOOPER_EVENT_OUTPUT; + } + if (handledEvents != events) { + LOG(FATAL) << "Mismatch: handledEvents=" << handledEvents << ", events=" << events; + } + return KEEP_CALLBACK; +} + +void InputConsumerNoResampling::processOutboundEvents() { + while (!mOutboundQueue.empty()) { + const InputMessage& outboundMsg = mOutboundQueue.front(); + + const status_t result = mChannel->sendMessage(&outboundMsg); + if (result == OK) { + if (outboundMsg.header.type == InputMessage::Type::FINISHED) { + ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/outboundMsg.header.seq); + } + // Successful send. Erase the entry and keep trying to send more + mOutboundQueue.pop(); + continue; + } + + // Publisher is busy, try again later. Keep this entry (do not erase) + if (result == WOULD_BLOCK) { + setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT); + return; // try again later + } + + // Some other error. Give up + LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName() + << "'. status=" << statusToString(result) << "(" << result << ")"; + } + + // The queue is now empty. Tell looper there's no more output to expect. + setFdEvents(ALOOPER_EVENT_INPUT); +} + +void InputConsumerNoResampling::finishInputEvent(uint32_t seq, bool handled) { + ensureCalledOnLooperThread(__func__); + mOutboundQueue.push(createFinishedMessage(seq, handled, popConsumeTime(seq))); + // also produce finish events for all batches for this seq (if any) + const auto it = mBatchedSequenceNumbers.find(seq); + if (it != mBatchedSequenceNumbers.end()) { + for (uint32_t subSeq : it->second) { + mOutboundQueue.push(createFinishedMessage(subSeq, handled, popConsumeTime(subSeq))); + } + mBatchedSequenceNumbers.erase(it); + } + processOutboundEvents(); +} + +bool InputConsumerNoResampling::probablyHasInput() const { + // Ideally, this would only be allowed to run on the looper thread, and in production, it will. + // However, for testing, it's convenient to call this while the looper thread is blocked, so + // we do not call ensureCalledOnLooperThread here. + return (!mBatches.empty()) || mChannel->probablyHasInput(); +} + +void InputConsumerNoResampling::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, + nsecs_t presentTime) { + ensureCalledOnLooperThread(__func__); + mOutboundQueue.push(createTimelineMessage(inputEventId, gpuCompletedTime, presentTime)); + processOutboundEvents(); +} + +nsecs_t InputConsumerNoResampling::popConsumeTime(uint32_t seq) { + auto it = mConsumeTimes.find(seq); + // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was + // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed. + LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32, + seq); + nsecs_t consumeTime = it->second; + mConsumeTimes.erase(it); + return consumeTime; +} + +void InputConsumerNoResampling::setFdEvents(int events) { + if (mFdEvents != events) { + mFdEvents = events; + if (events != 0) { + mLooper->addFd(mChannel->getFd(), 0, events, mCallback, nullptr); + } else { + mLooper->removeFd(mChannel->getFd()); + } + } +} + +void InputConsumerNoResampling::handleMessages(std::vector&& messages) { + // TODO(b/297226446) : add resampling + for (const InputMessage& msg : messages) { + if (msg.header.type == InputMessage::Type::MOTION) { + const int32_t action = msg.body.motion.action; + const DeviceId deviceId = msg.body.motion.deviceId; + const int32_t source = msg.body.motion.source; + const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE || + action == AMOTION_EVENT_ACTION_HOVER_MOVE) && + (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) || + isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK)); + if (batchableEvent) { + // add it to batch + mBatches[deviceId].emplace(msg); + } else { + // consume all pending batches for this event immediately + // TODO(b/329776327): figure out if this could be smarter by limiting the + // consumption only to the current device. + consumeBatchedInputEvents(std::nullopt); + handleMessage(msg); + } + } else { + // Non-motion events shouldn't force the consumption of pending batched events + handleMessage(msg); + } + } + // At the end of this, if we still have pending batches, notify the receiver about it. + + // We need to carefully notify the InputConsumerCallbacks about the pending batch. The receiver + // could choose to consume all events when notified about the batch. That means that the + // "mBatches" variable could change when 'InputConsumerCallbacks::onBatchedInputEventPending' is + // invoked. We also can't notify the InputConsumerCallbacks in a while loop until mBatches is + // empty, because the receiver could choose to not consume the batch immediately. + std::set pendingBatchSources; + for (const auto& [_, pendingMessages] : mBatches) { + // Assume that all messages for a given device has the same source. + pendingBatchSources.insert(pendingMessages.front().body.motion.source); + } + for (const int32_t source : pendingBatchSources) { + const bool sourceStillRemaining = + std::any_of(mBatches.begin(), mBatches.end(), [=](const auto& pair) { + return pair.second.front().body.motion.source == source; + }); + if (sourceStillRemaining) { + mCallbacks.onBatchedInputEventPending(source); + } + } +} + +std::vector InputConsumerNoResampling::readAllMessages() { + std::vector messages; + while (true) { + InputMessage msg; + status_t result = mChannel->receiveMessage(&msg); + switch (result) { + case OK: { + const auto [_, inserted] = + mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC)); + LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32, + msg.header.seq); + + // Trace the event processing timeline - event was just read from the socket + // TODO(b/329777420): distinguish between multiple instances of InputConsumer + // in the same process. + ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq); + messages.push_back(msg); + break; + } + case WOULD_BLOCK: { + return messages; + } + case DEAD_OBJECT: { + LOG(FATAL) << "Got a dead object for " << mChannel->getName(); + break; + } + case BAD_VALUE: { + LOG(FATAL) << "Got a bad value for " << mChannel->getName(); + break; + } + default: { + LOG(FATAL) << "Unexpected error: " << result; + break; + } + } + } +} + +void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const { + switch (msg.header.type) { + case InputMessage::Type::KEY: { + KeyEvent keyEvent; + initializeKeyEvent(keyEvent, msg); + mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq); + break; + } + + case InputMessage::Type::MOTION: { + MotionEvent motionEvent; + initializeMotionEvent(motionEvent, msg); + mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq); + break; + } + + case InputMessage::Type::FINISHED: + case InputMessage::Type::TIMELINE: { + LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by " + "InputConsumer on %s", + ftl::enum_string(msg.header.type).c_str(), + mChannel->getName().c_str()); + break; + } + + case InputMessage::Type::FOCUS: { + FocusEvent focusEvent; + initializeFocusEvent(focusEvent, msg); + mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq); + break; + } + + case InputMessage::Type::CAPTURE: { + CaptureEvent captureEvent; + initializeCaptureEvent(captureEvent, msg); + mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq); + break; + } + + case InputMessage::Type::DRAG: { + DragEvent dragEvent; + initializeDragEvent(dragEvent, msg); + mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq); + break; + } + + case InputMessage::Type::TOUCH_MODE: { + TouchModeEvent touchModeEvent; + initializeTouchModeEvent(touchModeEvent, msg); + mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq); + break; + } + } +} + +bool InputConsumerNoResampling::consumeBatchedInputEvents( + std::optional requestedFrameTime) { + ensureCalledOnLooperThread(__func__); + // When batching is not enabled, we want to consume all events. That's equivalent to having an + // infinite frameTime. + const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits::max()); + bool producedEvents = false; + for (auto& [deviceId, messages] : mBatches) { + MotionEvent motion; + std::optional firstSeqForBatch; + std::vector sequences; + while (!messages.empty()) { + const InputMessage& msg = messages.front(); + if (msg.body.motion.eventTime > frameTime) { + break; + } + if (!firstSeqForBatch.has_value()) { + initializeMotionEvent(motion, msg); + firstSeqForBatch = msg.header.seq; + const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}}); + if (!inserted) { + LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!"; + } + } else { + addSample(motion, msg); + mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq); + } + messages.pop(); + } + if (firstSeqForBatch.has_value()) { + mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch); + producedEvents = true; + } else { + // This is OK, it just means that the frameTime is too old (all events that we have + // pending are in the future of the frametime). Maybe print a + // warning? If there are multiple devices active though, this might be normal and can + // just be ignored, unless none of them resulted in any consumption (in that case, this + // function would already return "false" so we could just leave it up to the caller). + } + } + std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); }); + return producedEvents; +} + +void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const { + sp callingThreadLooper = Looper::getForThread(); + if (callingThreadLooper != mLooper) { + LOG(FATAL) << "The function " << func << " can only be called on the looper thread"; + } +} + +std::string InputConsumerNoResampling::dump() const { + ensureCalledOnLooperThread(__func__); + std::string out; + if (mOutboundQueue.empty()) { + out += "mOutboundQueue: \n"; + } else { + out += "mOutboundQueue:\n"; + // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue + // doesn't provide a good way to iterate over the entire container. + std::queue tmpQueue = mOutboundQueue; + while (!tmpQueue.empty()) { + out += std::string(" ") + outboundMessageToString(tmpQueue.front()) + "\n"; + tmpQueue.pop(); + } + } + + if (mBatches.empty()) { + out += "mBatches: \n"; + } else { + out += "mBatches:\n"; + for (const auto& [deviceId, messages] : mBatches) { + out += " Device id "; + out += std::to_string(deviceId); + out += ":\n"; + // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue + // doesn't provide a good way to iterate over the entire container. + std::queue tmpQueue = messages; + while (!tmpQueue.empty()) { + LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION); + MotionEvent motion; + initializeMotionEvent(motion, tmpQueue.front()); + out += std::string(" ") + streamableToString(motion) + "\n"; + tmpQueue.pop(); + } + } + } + + return out; +} + +} // namespace android diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 93af4c2066..e67a65a114 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -19,6 +19,7 @@ cc_test { "InputDevice_test.cpp", "InputEvent_test.cpp", "InputPublisherAndConsumer_test.cpp", + "InputPublisherAndConsumerNoResampling_test.cpp", "InputVerifier_test.cpp", "MotionPredictor_test.cpp", "MotionPredictorMetricsManager_test.cpp", diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp new file mode 100644 index 0000000000..e24ae49c09 --- /dev/null +++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp @@ -0,0 +1,807 @@ +/* + * Copyright 2024 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using android::base::Result; + +namespace android { + +namespace { + +static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; +static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +static constexpr int32_t POINTER_1_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +static constexpr int32_t POINTER_2_DOWN = + AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + +static auto constexpr TIMEOUT = 5s; + +struct Pointer { + int32_t id; + float x; + float y; + bool isResampled = false; +}; + +// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct +// allow to check the expectations against the event acquired from the InputConsumerCallbacks. To +// help simplify expectation checking it carries members not present in MotionEvent, like +// |rawXScale|. +struct PublishMotionArgs { + const int32_t action; + const nsecs_t downTime; + const uint32_t seq; + const int32_t eventId; + const int32_t deviceId = 1; + const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; + const int32_t displayId = ADISPLAY_ID_DEFAULT; + const int32_t actionButton = 0; + const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; + const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; + const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY; + const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE; + const float xScale = 2; + const float yScale = 3; + const float xOffset = -10; + const float yOffset = -20; + const float rawXScale = 4; + const float rawYScale = -5; + const float rawXOffset = -11; + const float rawYOffset = 42; + const float xPrecision = 0.25; + const float yPrecision = 0.5; + const float xCursorPosition = 1.3; + const float yCursorPosition = 50.6; + std::array hmac; + int32_t flags; + ui::Transform transform; + ui::Transform rawTransform; + const nsecs_t eventTime; + size_t pointerCount; + std::vector pointerProperties; + std::vector pointerCoords; + + PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector& pointers, + const uint32_t seq); +}; + +PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime, + const std::vector& pointers, const uint32_t inSeq) + : action(inAction), + downTime(inDownTime), + seq(inSeq), + eventId(InputEvent::nextId()), + eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) { + hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; + + flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + if (action == AMOTION_EVENT_ACTION_CANCEL) { + flags |= AMOTION_EVENT_FLAG_CANCELED; + } + pointerCount = pointers.size(); + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties.push_back({}); + pointerProperties[i].clear(); + pointerProperties[i].id = pointers[i].id; + pointerProperties[i].toolType = ToolType::FINGER; + + pointerCoords.push_back({}); + pointerCoords[i].clear(); + pointerCoords[i].isResampled = pointers[i].isResampled; + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i); + pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i); + } + transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1}); + rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1}); +} + +// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point +// comparisons limit precision to EPSILON. +void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) { + EXPECT_EQ(args.eventId, motionEvent.getId()); + EXPECT_EQ(args.deviceId, motionEvent.getDeviceId()); + EXPECT_EQ(args.source, motionEvent.getSource()); + EXPECT_EQ(args.displayId, motionEvent.getDisplayId()); + EXPECT_EQ(args.hmac, motionEvent.getHmac()); + EXPECT_EQ(args.action, motionEvent.getAction()); + EXPECT_EQ(args.downTime, motionEvent.getDownTime()); + EXPECT_EQ(args.flags, motionEvent.getFlags()); + EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags()); + EXPECT_EQ(args.metaState, motionEvent.getMetaState()); + EXPECT_EQ(args.buttonState, motionEvent.getButtonState()); + EXPECT_EQ(args.classification, motionEvent.getClassification()); + EXPECT_EQ(args.transform, motionEvent.getTransform()); + EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset, + motionEvent.getRawXOffset(), EPSILON); + EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset, + motionEvent.getRawYOffset(), EPSILON); + EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision()); + EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision()); + EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON); + EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON); + EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(), + EPSILON); + EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(), + EPSILON); + EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform()); + EXPECT_EQ(args.eventTime, motionEvent.getEventTime()); + EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount()); + EXPECT_EQ(0U, motionEvent.getHistorySize()); + + for (size_t i = 0; i < args.pointerCount; i++) { + SCOPED_TRACE(i); + EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i)); + EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i)); + + const auto& pc = args.pointerCoords[i]; + EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]); + + EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON); + EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON); + EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON); + EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i)); + EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i)); + + // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is + // "up", and the positive y direction is "down". + const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); + const float x = sinf(unscaledOrientation) * args.xScale; + const float y = -cosf(unscaledOrientation) * args.yScale; + EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i)); + } +} + +void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) { + status_t status = + publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId, + a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags, + a.metaState, a.buttonState, a.classification, a.transform, + a.xPrecision, a.yPrecision, a.xCursorPosition, + a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime, + a.pointerCount, a.pointerProperties.data(), + a.pointerCoords.data()); + ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK"; +} + +Result receiveConsumerResponse( + InputPublisher& publisher, std::chrono::milliseconds timeout) { + const std::chrono::time_point start = std::chrono::steady_clock::now(); + + while (true) { + Result result = publisher.receiveConsumerResponse(); + if (result.ok()) { + return result; + } + const std::chrono::duration waited = std::chrono::steady_clock::now() - start; + if (waited > timeout) { + return result; + } + } +} + +void verifyFinishedSignal(InputPublisher& publisher, uint32_t seq, nsecs_t publishTime) { + Result result = receiveConsumerResponse(publisher, TIMEOUT); + ASSERT_TRUE(result.ok()) << "receiveConsumerResponse returned " << result.error().message(); + ASSERT_TRUE(std::holds_alternative(*result)); + const InputPublisher::Finished& finish = std::get(*result); + ASSERT_EQ(seq, finish.seq) + << "receiveConsumerResponse should have returned the original sequence number"; + ASSERT_TRUE(finish.handled) + << "receiveConsumerResponse should have set handled to consumer's reply"; + ASSERT_GE(finish.consumeTime, publishTime) + << "finished signal's consume time should be greater than publish time"; +} + +} // namespace + +class InputConsumerMessageHandler : public MessageHandler { +public: + InputConsumerMessageHandler(std::function function) + : mFunction(function) {} + +private: + void handleMessage(const Message& message) override { mFunction(message); } + + std::function mFunction; +}; + +class InputPublisherAndConsumerNoResamplingTest : public testing::Test, + public InputConsumerCallbacks { +protected: + std::unique_ptr mClientChannel; + std::unique_ptr mPublisher; + std::unique_ptr mConsumer; + + std::thread mLooperThread; + sp mLooper = sp::make(/*allowNonCallbacks=*/false); + + // LOOPER CONTROL + // Set to false when you want the looper to exit + std::atomic mExitLooper = false; + std::mutex mLock; + + // Used by test to notify looper that the value of "mLooperMayProceed" has changed + std::condition_variable mNotifyLooperMayProceed; + bool mLooperMayProceed GUARDED_BY(mLock){true}; + // Used by looper to notify the test that it's about to block on "mLooperMayProceed" -> true + std::condition_variable mNotifyLooperWaiting; + bool mLooperIsBlocked GUARDED_BY(mLock){false}; + + std::condition_variable mNotifyConsumerDestroyed; + bool mConsumerDestroyed GUARDED_BY(mLock){false}; + + void runLooper() { + static constexpr int LOOP_INDEFINITELY = -1; + Looper::setForThread(mLooper); + // Loop forever -- this thread is dedicated to servicing the looper callbacks. + while (!mExitLooper) { + mLooper->pollOnce(/*timeoutMillis=*/LOOP_INDEFINITELY); + } + } + + void SetUp() override { + std::unique_ptr serverChannel; + status_t result = + InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel); + ASSERT_EQ(OK, result); + + mPublisher = std::make_unique(std::move(serverChannel)); + mMessageHandler = sp::make( + [this](const Message& message) { handleMessage(message); }); + mLooperThread = std::thread([this] { runLooper(); }); + sendMessage(LooperMessage::CREATE_CONSUMER); + } + + void publishAndConsumeKeyEvent(); + void publishAndConsumeMotionStream(); + void publishAndConsumeMotionDown(nsecs_t downTime); + void publishAndConsumeBatchedMotionMove(nsecs_t downTime); + void publishAndConsumeFocusEvent(); + void publishAndConsumeCaptureEvent(); + void publishAndConsumeDragEvent(); + void publishAndConsumeTouchModeEvent(); + void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime, + const std::vector& pointers); + void TearDown() override { + // Destroy the consumer, flushing any of the pending ack's. + sendMessage(LooperMessage::DESTROY_CONSUMER); + { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + mNotifyConsumerDestroyed.wait(lock, [this] { return mConsumerDestroyed; }); + } + // Stop the looper thread so that we can destroy the object. + mExitLooper = true; + mLooper->wake(); + mLooperThread.join(); + } + +protected: + // Interaction with the looper thread + enum class LooperMessage : int { + CALL_PROBABLY_HAS_INPUT, + CREATE_CONSUMER, + DESTROY_CONSUMER, + CALL_REPORT_TIMELINE, + BLOCK_LOOPER, + }; + void sendMessage(LooperMessage message); + struct ReportTimelineArgs { + int32_t inputEventId; + nsecs_t gpuCompletedTime; + nsecs_t presentTime; + }; + // The input to the function "InputConsumer::reportTimeline". Populated on the test thread and + // accessed on the looper thread. + BlockingQueue mReportTimelineArgs; + // The output of calling "InputConsumer::probablyHasInput()". Populated on the looper thread and + // accessed on the test thread. + BlockingQueue mProbablyHasInputResponses; + +private: + sp mMessageHandler; + void handleMessage(const Message& message); + + static auto constexpr NO_EVENT_TIMEOUT = 10ms; + // The sequence number to use when publishing the next event + uint32_t mSeq = 1; + + BlockingQueue mKeyEvents; + BlockingQueue mMotionEvents; + BlockingQueue mFocusEvents; + BlockingQueue mCaptureEvents; + BlockingQueue mDragEvents; + BlockingQueue mTouchModeEvents; + + // InputConsumerCallbacks interface + void onKeyEvent(KeyEvent&& event, uint32_t seq) override { + mKeyEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onMotionEvent(MotionEvent&& event, uint32_t seq) override { + mMotionEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onBatchedInputEventPending(int32_t pendingBatchSource) override { + if (!mConsumer->probablyHasInput()) { + ADD_FAILURE() << "should deterministically have input because there is a batch"; + } + mConsumer->consumeBatchedInputEvents(std::nullopt); + }; + void onFocusEvent(FocusEvent&& event, uint32_t seq) override { + mFocusEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + }; + void onCaptureEvent(CaptureEvent&& event, uint32_t seq) override { + mCaptureEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + }; + void onDragEvent(DragEvent&& event, uint32_t seq) override { + mDragEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + } + void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) override { + mTouchModeEvents.push(std::move(event)); + mConsumer->finishInputEvent(seq, true); + }; +}; + +void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) { + Message msg{ftl::to_underlying(message)}; + mLooper->sendMessage(mMessageHandler, msg); +} + +void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) { + switch (static_cast(message.what)) { + case LooperMessage::CALL_PROBABLY_HAS_INPUT: { + mProbablyHasInputResponses.push(mConsumer->probablyHasInput()); + break; + } + case LooperMessage::CREATE_CONSUMER: { + mConsumer = std::make_unique(std::move(mClientChannel), + mLooper, *this); + break; + } + case LooperMessage::DESTROY_CONSUMER: { + mConsumer = nullptr; + { + std::unique_lock lock(mLock); + mConsumerDestroyed = true; + } + mNotifyConsumerDestroyed.notify_all(); + break; + } + case LooperMessage::CALL_REPORT_TIMELINE: { + std::optional args = mReportTimelineArgs.pop(); + if (!args.has_value()) { + ADD_FAILURE() << "Couldn't get the 'reportTimeline' args in time"; + return; + } + mConsumer->reportTimeline(args->inputEventId, args->gpuCompletedTime, + args->presentTime); + break; + } + case LooperMessage::BLOCK_LOOPER: { + { + std::unique_lock lock(mLock); + mLooperIsBlocked = true; + } + mNotifyLooperWaiting.notify_all(); + + { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + mNotifyLooperMayProceed.wait(lock, [this] { return mLooperMayProceed; }); + } + + { + std::unique_lock lock(mLock); + mLooperIsBlocked = false; + } + mNotifyLooperWaiting.notify_all(); + break; + } + } +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() { + status_t status; + + const uint32_t seq = mSeq++; + int32_t eventId = InputEvent::nextId(); + constexpr int32_t deviceId = 1; + constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD; + constexpr int32_t displayId = ADISPLAY_ID_DEFAULT; + constexpr std::array hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, + 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + constexpr int32_t action = AKEY_EVENT_ACTION_DOWN; + constexpr int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM; + constexpr int32_t keyCode = AKEYCODE_ENTER; + constexpr int32_t scanCode = 13; + constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; + constexpr int32_t repeatCount = 1; + constexpr nsecs_t downTime = 3; + constexpr nsecs_t eventTime = 4; + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + + status = mPublisher->publishKeyEvent(seq, eventId, deviceId, source, displayId, hmac, action, + flags, keyCode, scanCode, metaState, repeatCount, downTime, + eventTime); + ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK"; + + std::optional keyEvent = mKeyEvents.popWithTimeout(TIMEOUT); + + sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT); + std::optional probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT); + ASSERT_TRUE(probablyHasInput.has_value()); + ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed"; + + ASSERT_TRUE(keyEvent.has_value()) << "consumer should have returned non-NULL event"; + + EXPECT_EQ(eventId, keyEvent->getId()); + EXPECT_EQ(deviceId, keyEvent->getDeviceId()); + EXPECT_EQ(source, keyEvent->getSource()); + EXPECT_EQ(displayId, keyEvent->getDisplayId()); + EXPECT_EQ(hmac, keyEvent->getHmac()); + EXPECT_EQ(action, keyEvent->getAction()); + EXPECT_EQ(flags, keyEvent->getFlags()); + EXPECT_EQ(keyCode, keyEvent->getKeyCode()); + EXPECT_EQ(scanCode, keyEvent->getScanCode()); + EXPECT_EQ(metaState, keyEvent->getMetaState()); + EXPECT_EQ(repeatCount, keyEvent->getRepeatCount()); + EXPECT_EQ(downTime, keyEvent->getDownTime()); + EXPECT_EQ(eventTime, keyEvent->getEventTime()); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionStream() { + const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC); + + publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}}); + + publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}, + Pointer{.id = 1, .x = 200, .y = 300}}); + + publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}, + Pointer{.id = 1, .x = 200, .y = 300}, + Pointer{.id = 2, .x = 300, .y = 400}}); + + // Provide a consistent input stream - cancel the gesture that was started above + publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}, + Pointer{.id = 1, .x = 200, .y = 300}, + Pointer{.id = 2, .x = 300, .y = 400}}); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionDown(nsecs_t downTime) { + publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}}); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMove( + nsecs_t downTime) { + uint32_t seq = mSeq++; + const std::vector pointers = {Pointer{.id = 0, .x = 20, .y = 30}}; + PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq); + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + + // Block the looper thread, preventing it from being able to service any of the fd callbacks. + + { + std::scoped_lock lock(mLock); + mLooperMayProceed = false; + } + sendMessage(LooperMessage::BLOCK_LOOPER); + { + std::unique_lock lock(mLock); + mNotifyLooperWaiting.wait(lock, [this] { return mLooperIsBlocked; }); + } + + publishMotionEvent(*mPublisher, args); + + // Ensure no event arrives because the UI thread is blocked + std::optional noEvent = mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT); + ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent; + + Result result = mPublisher->receiveConsumerResponse(); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(WOULD_BLOCK, result.error().code()); + + // We shouldn't be calling mConsumer on the UI thread, but in this situation, the looper + // thread is locked, so this should be safe to do. + ASSERT_TRUE(mConsumer->probablyHasInput()) + << "should deterministically have input because there is a batch"; + + // Now, unblock the looper thread, so that the event can arrive. + { + std::scoped_lock lock(mLock); + mLooperMayProceed = true; + } + mNotifyLooperMayProceed.notify_all(); + + std::optional motion = mMotionEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(motion.has_value()); + ASSERT_EQ(ACTION_MOVE, motion->getAction()); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent( + int32_t action, nsecs_t downTime, const std::vector& pointers) { + uint32_t seq = mSeq++; + PublishMotionArgs args(action, downTime, pointers, seq); + nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + publishMotionEvent(*mPublisher, args); + + std::optional event = mMotionEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event"; + + verifyArgsEqualToEvent(args, *event); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeFocusEvent() { + status_t status; + + constexpr uint32_t seq = 15; + int32_t eventId = InputEvent::nextId(); + constexpr bool hasFocus = true; + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + + status = mPublisher->publishFocusEvent(seq, eventId, hasFocus); + ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK"; + + std::optional focusEvent = mFocusEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(focusEvent.has_value()) << "consumer should have returned non-NULL event"; + EXPECT_EQ(eventId, focusEvent->getId()); + EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeCaptureEvent() { + status_t status; + + constexpr uint32_t seq = 42; + int32_t eventId = InputEvent::nextId(); + constexpr bool captureEnabled = true; + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + + status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled); + ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK"; + + std::optional event = mCaptureEvents.popWithTimeout(TIMEOUT); + + ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event"; + + const CaptureEvent& captureEvent = *event; + EXPECT_EQ(eventId, captureEvent.getId()); + EXPECT_EQ(captureEnabled, captureEvent.getPointerCaptureEnabled()); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeDragEvent() { + status_t status; + + constexpr uint32_t seq = 15; + int32_t eventId = InputEvent::nextId(); + constexpr bool isExiting = false; + constexpr float x = 10; + constexpr float y = 15; + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + + status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting); + ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK"; + + std::optional event = mDragEvents.popWithTimeout(TIMEOUT); + + ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event"; + + const DragEvent& dragEvent = *event; + EXPECT_EQ(eventId, dragEvent.getId()); + EXPECT_EQ(isExiting, dragEvent.isExiting()); + EXPECT_EQ(x, dragEvent.getX()); + EXPECT_EQ(y, dragEvent.getY()); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent() { + status_t status; + + constexpr uint32_t seq = 15; + int32_t eventId = InputEvent::nextId(); + constexpr bool touchModeEnabled = true; + const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); + + status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled); + ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK"; + + std::optional event = mTouchModeEvents.popWithTimeout(TIMEOUT); + ASSERT_NE(std::nullopt, event); + + const TouchModeEvent& touchModeEvent = *event; + EXPECT_EQ(eventId, touchModeEvent.getId()); + EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode()); + + verifyFinishedSignal(*mPublisher, seq, publishTime); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) { + const int32_t inputEventId = 20; + const nsecs_t gpuCompletedTime = 30; + const nsecs_t presentTime = 40; + + mReportTimelineArgs.emplace(inputEventId, gpuCompletedTime, presentTime); + sendMessage(LooperMessage::CALL_REPORT_TIMELINE); + + Result result = receiveConsumerResponse(*mPublisher, TIMEOUT); + ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK"; + ASSERT_TRUE(std::holds_alternative(*result)); + const InputPublisher::Timeline& timeline = std::get(*result); + ASSERT_EQ(inputEventId, timeline.inputEventId); + ASSERT_EQ(gpuCompletedTime, timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]); + ASSERT_EQ(presentTime, timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishKeyEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent()); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream()); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionMoveEvent_EndToEnd) { + // Publish a DOWN event before MOVE to pass the InputVerifier checks. + const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime)); + + // Publish the MOVE event and check expectations. + ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime)); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishFocusEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent()); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishCaptureEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent()); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishDragEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent()); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishTouchModeEvent_EndToEnd) { + ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent()); +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, + PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) { + status_t status; + const size_t pointerCount = 1; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties[i].clear(); + pointerCoords[i].clear(); + } + + ui::Transform identityTransform; + status = + mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, + 0, 0, 0, MotionClassification::NONE, identityTransform, + 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, + 0, 0, pointerCount, pointerProperties, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, + PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) { + status_t status; + const size_t pointerCount = 0; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + + ui::Transform identityTransform; + status = + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, + 0, 0, 0, MotionClassification::NONE, identityTransform, + 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, + 0, 0, pointerCount, pointerProperties, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, + PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) { + status_t status; + const size_t pointerCount = MAX_POINTERS + 1; + PointerProperties pointerProperties[pointerCount]; + PointerCoords pointerCoords[pointerCount]; + for (size_t i = 0; i < pointerCount; i++) { + pointerProperties[i].clear(); + pointerCoords[i].clear(); + } + + ui::Transform identityTransform; + status = + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, + 0, 0, 0, MotionClassification::NONE, identityTransform, + 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, + 0, 0, pointerCount, pointerProperties, pointerCoords); + ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; +} + +TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMultipleEvents_EndToEnd) { + const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC); + + publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}}); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent()); + publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}, + Pointer{.id = 1, .x = 200, .y = 300}}); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent()); + publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}, + Pointer{.id = 1, .x = 200, .y = 300}, + Pointer{.id = 2, .x = 200, .y = 300}}); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent()); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent()); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent()); + // Provide a consistent input stream - cancel the gesture that was started above + publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime, + {Pointer{.id = 0, .x = 20, .y = 30}, + Pointer{.id = 1, .x = 200, .y = 300}, + Pointer{.id = 2, .x = 200, .y = 300}}); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent()); + ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent()); +} + +} // namespace android -- GitLab From 788cba81c0e4115e68577f854974ff0c52fc3b5d Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 15 Mar 2024 11:29:11 -0400 Subject: [PATCH 085/465] SF: Short-circuit late touch boost Avoid throwaway work (especially rankFrameRates) in the common path. Bug: 329111930 Test: libsurfaceflinger_unittest Change-Id: If5766896feab61e6e2d0c0b6a425ffe983b24232 --- .../Scheduler/RefreshRateSelector.cpp | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index ad59f1a574..483ea894a2 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -831,7 +831,7 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector bool { if (supportsAppFrameRateOverrideByContent()) { // Enable touch boost if there are other layers besides exact return explicitExact + noVoteLayers + explicitGteLayers != layers.size(); @@ -839,13 +839,11 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector bool { + return explicitCategoryVoteLayers + noVoteLayers + explicitGteLayers != layers.size(); + }; // A method for UI Toolkit to send the touch signal via "HighHint" category vote, // which will touch boost when there are no ExplicitDefault layer votes. This is an @@ -853,13 +851,17 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector 0; - if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact && - touchBoostForCategory && - scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) { - ALOGV("Touch Boost"); - ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])", - to_string(touchRefreshRates.front().frameRateMode.fps).c_str()); - return {touchRefreshRates, GlobalSignals{.touch = true}}; + if (hasInteraction && explicitDefaultVoteLayers == 0 && isTouchBoostForExplicitExact() && + isTouchBoostForCategory()) { + const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending); + using fps_approx_ops::operator<; + + if (scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) { + ALOGV("Touch Boost"); + ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])", + to_string(touchRefreshRates.front().frameRateMode.fps).c_str()); + return {touchRefreshRates, GlobalSignals{.touch = true}}; + } } // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the @@ -873,8 +875,8 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector Date: Fri, 15 Mar 2024 16:12:52 +0000 Subject: [PATCH 086/465] Revert "swapchain: support RGBX backed opaque swapchain for offs..." Revert submission 26542013 Reason for revert: DroidMonitor: Potential culprit for Bug b/329753804 - verifying through ABTD before revert submission. This is part of the standard investigation process, and does not mean your CL will be reverted. Reverted changes: /q/submissionid:26542013 Change-Id: I6ec4eba91fc62cd8e8ab631722fd2a9ec129aa30 --- vulkan/libvulkan/swapchain.cpp | 40 +++++++++++----------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 6ba2eb1ad7..74d3d9dc64 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -526,15 +526,12 @@ void copy_ready_timings(Swapchain& swapchain, *count = num_copied; } -PixelFormat GetNativePixelFormat(VkFormat format, - VkCompositeAlphaFlagBitsKHR alpha) { +PixelFormat GetNativePixelFormat(VkFormat format) { PixelFormat native_format = PixelFormat::RGBA_8888; switch (format) { case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R8G8B8A8_SRGB: - native_format = alpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR - ? PixelFormat::RGBX_8888 - : PixelFormat::RGBA_8888; + native_format = PixelFormat::RGBA_8888; break; case VK_FORMAT_R5G6B5_UNORM_PACK16: native_format = PixelFormat::RGB_565; @@ -904,7 +901,7 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { ATRACE_CALL(); - auto surface_handle = pSurfaceInfo->surface; + auto surface = pSurfaceInfo->surface; auto capabilities = &pSurfaceCapabilities->surfaceCapabilities; VkSurfacePresentModeEXT const *pPresentMode = nullptr; @@ -925,13 +922,7 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( int transform_hint; int max_buffer_count; int min_undequeued_buffers; - // On Android, window composition is a WindowManager property, not something - // associated with the bufferqueue. It can't be changed from here for a - // swapchain connected with SurfaceFlinger. For offscreen surfaces, it's - // allowed to report opaque being supported for RGBX preference. - VkCompositeAlphaFlagsKHR composite_alpha = - VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; - if (surface_handle == VK_NULL_HANDLE) { + if (surface == VK_NULL_HANDLE) { const InstanceData& instance_data = GetData(physicalDevice); ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query; bool surfaceless_enabled = @@ -952,8 +943,7 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( capabilities->minImageCount = 0xFFFFFFFF; capabilities->maxImageCount = 0xFFFFFFFF; } else { - Surface& surface = *SurfaceFromHandle(surface_handle); - ANativeWindow* window = surface.window.get(); + ANativeWindow* window = SurfaceFromHandle(surface)->window.get(); err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width); if (err != android::OK) { @@ -1028,11 +1018,6 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( min_undequeued_buffers + default_additional_buffers); capabilities->maxImageCount = static_cast(max_buffer_count); } - - if (!(surface.consumer_usage & - AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY)) { - composite_alpha |= VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - } } capabilities->currentExtent = @@ -1059,7 +1044,9 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR( capabilities->currentTransform = TranslateNativeToVulkanTransform(transform_hint); - capabilities->supportedCompositeAlpha = composite_alpha; + // On Android, window composition is a WindowManager property, not something + // associated with the bufferqueue. It can't be changed from here. + capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; capabilities->supportedUsageFlags = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | @@ -1658,21 +1645,20 @@ VkResult CreateSwapchainKHR(VkDevice device, ALOGV("vkCreateSwapchainKHR: surface=0x%" PRIx64 " minImageCount=%u imageFormat=%u imageColorSpace=%u" - " imageExtent=%ux%u imageUsage=%#x preTransform=%u compositeAlpha=%u" - " presentMode=%u oldSwapchain=0x%" PRIx64, + " imageExtent=%ux%u imageUsage=%#x preTransform=%u presentMode=%u" + " oldSwapchain=0x%" PRIx64, reinterpret_cast(create_info->surface), create_info->minImageCount, create_info->imageFormat, create_info->imageColorSpace, create_info->imageExtent.width, create_info->imageExtent.height, create_info->imageUsage, - create_info->preTransform, create_info->compositeAlpha, - create_info->presentMode, + create_info->preTransform, create_info->presentMode, reinterpret_cast(create_info->oldSwapchain)); if (!allocator) allocator = &GetData(device).allocator; - PixelFormat native_pixel_format = GetNativePixelFormat( - create_info->imageFormat, create_info->compositeAlpha); + PixelFormat native_pixel_format = + GetNativePixelFormat(create_info->imageFormat); DataSpace native_dataspace = GetNativeDataspace( create_info->imageColorSpace, create_info->imageFormat); if (native_dataspace == DataSpace::UNKNOWN) { -- GitLab From 3891ec978149a896a453b4a675c4134639b9a152 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 15 Mar 2024 13:29:57 -0700 Subject: [PATCH 087/465] Send events wrapped in unique_ptr to InputConsumerCallbacks This will allow the upper layers to release the ownership and hand the object over to the ndk client. The client will then assume the ownership. This is not possible with the current implementation of rvalue, because the ndk interface is only using pointers. Unfortunately, this makes things more clunky for tests (unless we modify BlockingQueue). Bug: 324271765 Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_repeat=100 --gtest_break_on_failure Change-Id: I0cf326a22f840be6f8aa00d1e69f818815788487 --- include/input/InputConsumerNoResampling.h | 12 +-- libs/input/InputConsumerNoResampling.cpp | 96 ++++++++++--------- ...tPublisherAndConsumerNoResampling_test.cpp | 66 +++++++------ 3 files changed, 93 insertions(+), 81 deletions(-) diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h index 7e6ae9b107..9e48b0872d 100644 --- a/include/input/InputConsumerNoResampling.h +++ b/include/input/InputConsumerNoResampling.h @@ -29,8 +29,8 @@ namespace android { class InputConsumerCallbacks { public: virtual ~InputConsumerCallbacks(){}; - virtual void onKeyEvent(KeyEvent&& event, uint32_t seq) = 0; - virtual void onMotionEvent(MotionEvent&& event, uint32_t seq) = 0; + virtual void onKeyEvent(std::unique_ptr event, uint32_t seq) = 0; + virtual void onMotionEvent(std::unique_ptr event, uint32_t seq) = 0; /** * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents". * If you don't want batching, then call "consumeBatchedInputEvents" immediately with @@ -38,10 +38,10 @@ public: * @param pendingBatchSource the source of the pending batch. */ virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0; - virtual void onFocusEvent(FocusEvent&& event, uint32_t seq) = 0; - virtual void onCaptureEvent(CaptureEvent&& event, uint32_t seq) = 0; - virtual void onDragEvent(DragEvent&& event, uint32_t seq) = 0; - virtual void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) = 0; + virtual void onFocusEvent(std::unique_ptr event, uint32_t seq) = 0; + virtual void onCaptureEvent(std::unique_ptr event, uint32_t seq) = 0; + virtual void onDragEvent(std::unique_ptr event, uint32_t seq) = 0; + virtual void onTouchModeEvent(std::unique_ptr event, uint32_t seq) = 0; }; /** diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp index 1462c9059a..52acb51910 100644 --- a/libs/input/InputConsumerNoResampling.cpp +++ b/libs/input/InputConsumerNoResampling.cpp @@ -44,28 +44,37 @@ namespace { const bool DEBUG_TRANSPORT_CONSUMER = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO); -void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) { - event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, - msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, - msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, - msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, - msg.body.key.eventTime); +std::unique_ptr createKeyEvent(const InputMessage& msg) { + std::unique_ptr event = std::make_unique(); + event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, + msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, + msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, + msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, + msg.body.key.eventTime); + return event; } -void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) { - event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus); +std::unique_ptr createFocusEvent(const InputMessage& msg) { + std::unique_ptr event = std::make_unique(); + event->initialize(msg.body.focus.eventId, msg.body.focus.hasFocus); + return event; } -void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) { - event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled); +std::unique_ptr createCaptureEvent(const InputMessage& msg) { + std::unique_ptr event = std::make_unique(); + event->initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled); + return event; } -void initializeDragEvent(DragEvent& event, const InputMessage& msg) { - event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y, - msg.body.drag.isExiting); +std::unique_ptr createDragEvent(const InputMessage& msg) { + std::unique_ptr event = std::make_unique(); + event->initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y, + msg.body.drag.isExiting); + return event; } -void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { +std::unique_ptr createMotionEvent(const InputMessage& msg) { + std::unique_ptr event = std::make_unique(); const uint32_t pointerCount = msg.body.motion.pointerCount; std::vector pointerProperties; pointerProperties.reserve(pointerCount); @@ -83,15 +92,16 @@ void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw, msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, 0, 0, 1}); - event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, - msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, - msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, - msg.body.motion.metaState, msg.body.motion.buttonState, - msg.body.motion.classification, transform, msg.body.motion.xPrecision, - msg.body.motion.yPrecision, msg.body.motion.xCursorPosition, - msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime, - msg.body.motion.eventTime, pointerCount, pointerProperties.data(), - pointerCoords.data()); + event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, + msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, + msg.body.motion.actionButton, msg.body.motion.flags, + msg.body.motion.edgeFlags, msg.body.motion.metaState, + msg.body.motion.buttonState, msg.body.motion.classification, transform, + msg.body.motion.xPrecision, msg.body.motion.yPrecision, + msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition, + displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime, + pointerCount, pointerProperties.data(), pointerCoords.data()); + return event; } void addSample(MotionEvent& event, const InputMessage& msg) { @@ -107,8 +117,10 @@ void addSample(MotionEvent& event, const InputMessage& msg) { event.addSample(msg.body.motion.eventTime, pointerCoords.data()); } -void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) { - event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); +std::unique_ptr createTouchModeEvent(const InputMessage& msg) { + std::unique_ptr event = std::make_unique(); + event->initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode); + return event; } std::string outboundMessageToString(const InputMessage& outboundMsg) { @@ -388,15 +400,13 @@ std::vector InputConsumerNoResampling::readAllMessages() { void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const { switch (msg.header.type) { case InputMessage::Type::KEY: { - KeyEvent keyEvent; - initializeKeyEvent(keyEvent, msg); + std::unique_ptr keyEvent = createKeyEvent(msg); mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq); break; } case InputMessage::Type::MOTION: { - MotionEvent motionEvent; - initializeMotionEvent(motionEvent, msg); + std::unique_ptr motionEvent = createMotionEvent(msg); mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq); break; } @@ -411,29 +421,25 @@ void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const { } case InputMessage::Type::FOCUS: { - FocusEvent focusEvent; - initializeFocusEvent(focusEvent, msg); + std::unique_ptr focusEvent = createFocusEvent(msg); mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq); break; } case InputMessage::Type::CAPTURE: { - CaptureEvent captureEvent; - initializeCaptureEvent(captureEvent, msg); + std::unique_ptr captureEvent = createCaptureEvent(msg); mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq); break; } case InputMessage::Type::DRAG: { - DragEvent dragEvent; - initializeDragEvent(dragEvent, msg); + std::unique_ptr dragEvent = createDragEvent(msg); mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq); break; } case InputMessage::Type::TOUCH_MODE: { - TouchModeEvent touchModeEvent; - initializeTouchModeEvent(touchModeEvent, msg); + std::unique_ptr touchModeEvent = createTouchModeEvent(msg); mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq); break; } @@ -448,7 +454,7 @@ bool InputConsumerNoResampling::consumeBatchedInputEvents( const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits::max()); bool producedEvents = false; for (auto& [deviceId, messages] : mBatches) { - MotionEvent motion; + std::unique_ptr motion; std::optional firstSeqForBatch; std::vector sequences; while (!messages.empty()) { @@ -456,20 +462,21 @@ bool InputConsumerNoResampling::consumeBatchedInputEvents( if (msg.body.motion.eventTime > frameTime) { break; } - if (!firstSeqForBatch.has_value()) { - initializeMotionEvent(motion, msg); + if (motion == nullptr) { + motion = createMotionEvent(msg); firstSeqForBatch = msg.header.seq; const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}}); if (!inserted) { LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!"; } } else { - addSample(motion, msg); + addSample(*motion, msg); mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq); } messages.pop(); } - if (firstSeqForBatch.has_value()) { + if (motion != nullptr) { + LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value()); mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch); producedEvents = true; } else { @@ -520,9 +527,8 @@ std::string InputConsumerNoResampling::dump() const { std::queue tmpQueue = messages; while (!tmpQueue.empty()) { LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION); - MotionEvent motion; - initializeMotionEvent(motion, tmpQueue.front()); - out += std::string(" ") + streamableToString(motion) + "\n"; + std::unique_ptr motion = createMotionEvent(tmpQueue.front()); + out += std::string(" ") + streamableToString(*motion) + "\n"; tmpQueue.pop(); } } diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp index e24ae49c09..6593497763 100644 --- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp @@ -341,19 +341,19 @@ private: // The sequence number to use when publishing the next event uint32_t mSeq = 1; - BlockingQueue mKeyEvents; - BlockingQueue mMotionEvents; - BlockingQueue mFocusEvents; - BlockingQueue mCaptureEvents; - BlockingQueue mDragEvents; - BlockingQueue mTouchModeEvents; + BlockingQueue> mKeyEvents; + BlockingQueue> mMotionEvents; + BlockingQueue> mFocusEvents; + BlockingQueue> mCaptureEvents; + BlockingQueue> mDragEvents; + BlockingQueue> mTouchModeEvents; // InputConsumerCallbacks interface - void onKeyEvent(KeyEvent&& event, uint32_t seq) override { + void onKeyEvent(std::unique_ptr event, uint32_t seq) override { mKeyEvents.push(std::move(event)); mConsumer->finishInputEvent(seq, true); } - void onMotionEvent(MotionEvent&& event, uint32_t seq) override { + void onMotionEvent(std::unique_ptr event, uint32_t seq) override { mMotionEvents.push(std::move(event)); mConsumer->finishInputEvent(seq, true); } @@ -363,19 +363,19 @@ private: } mConsumer->consumeBatchedInputEvents(std::nullopt); }; - void onFocusEvent(FocusEvent&& event, uint32_t seq) override { + void onFocusEvent(std::unique_ptr event, uint32_t seq) override { mFocusEvents.push(std::move(event)); mConsumer->finishInputEvent(seq, true); }; - void onCaptureEvent(CaptureEvent&& event, uint32_t seq) override { + void onCaptureEvent(std::unique_ptr event, uint32_t seq) override { mCaptureEvents.push(std::move(event)); mConsumer->finishInputEvent(seq, true); }; - void onDragEvent(DragEvent&& event, uint32_t seq) override { + void onDragEvent(std::unique_ptr event, uint32_t seq) override { mDragEvents.push(std::move(event)); mConsumer->finishInputEvent(seq, true); } - void onTouchModeEvent(TouchModeEvent&& event, uint32_t seq) override { + void onTouchModeEvent(std::unique_ptr event, uint32_t seq) override { mTouchModeEvents.push(std::move(event)); mConsumer->finishInputEvent(seq, true); }; @@ -465,15 +465,15 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() { eventTime); ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK"; - std::optional keyEvent = mKeyEvents.popWithTimeout(TIMEOUT); + std::optional> optKeyEvent = mKeyEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optKeyEvent.has_value()) << "consumer should have returned non-NULL event"; + std::unique_ptr keyEvent = std::move(*optKeyEvent); sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT); std::optional probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT); ASSERT_TRUE(probablyHasInput.has_value()); ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed"; - ASSERT_TRUE(keyEvent.has_value()) << "consumer should have returned non-NULL event"; - EXPECT_EQ(eventId, keyEvent->getId()); EXPECT_EQ(deviceId, keyEvent->getDeviceId()); EXPECT_EQ(source, keyEvent->getSource()); @@ -540,7 +540,8 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMo publishMotionEvent(*mPublisher, args); // Ensure no event arrives because the UI thread is blocked - std::optional noEvent = mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT); + std::optional> noEvent = + mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT); ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent; Result result = mPublisher->receiveConsumerResponse(); @@ -559,8 +560,9 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMo } mNotifyLooperMayProceed.notify_all(); - std::optional motion = mMotionEvents.popWithTimeout(TIMEOUT); - ASSERT_TRUE(motion.has_value()); + std::optional> optMotion = mMotionEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optMotion.has_value()); + std::unique_ptr motion = std::move(*optMotion); ASSERT_EQ(ACTION_MOVE, motion->getAction()); verifyFinishedSignal(*mPublisher, seq, publishTime); @@ -573,8 +575,9 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent( nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC); publishMotionEvent(*mPublisher, args); - std::optional event = mMotionEvents.popWithTimeout(TIMEOUT); - ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event"; + std::optional> optMotion = mMotionEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optMotion.has_value()); + std::unique_ptr event = std::move(*optMotion); verifyArgsEqualToEvent(args, *event); @@ -592,8 +595,9 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeFocusEvent() { status = mPublisher->publishFocusEvent(seq, eventId, hasFocus); ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK"; - std::optional focusEvent = mFocusEvents.popWithTimeout(TIMEOUT); - ASSERT_TRUE(focusEvent.has_value()) << "consumer should have returned non-NULL event"; + std::optional> optFocusEvent = mFocusEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optFocusEvent.has_value()) << "consumer should have returned non-NULL event"; + std::unique_ptr focusEvent = std::move(*optFocusEvent); EXPECT_EQ(eventId, focusEvent->getId()); EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); @@ -611,9 +615,9 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeCaptureEvent() status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled); ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK"; - std::optional event = mCaptureEvents.popWithTimeout(TIMEOUT); - - ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event"; + std::optional> optEvent = mCaptureEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event"; + std::unique_ptr event = std::move(*optEvent); const CaptureEvent& captureEvent = *event; EXPECT_EQ(eventId, captureEvent.getId()); @@ -635,9 +639,9 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeDragEvent() { status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting); ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK"; - std::optional event = mDragEvents.popWithTimeout(TIMEOUT); - - ASSERT_TRUE(event.has_value()) << "consumer should have returned non-NULL event"; + std::optional> optEvent = mDragEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event"; + std::unique_ptr event = std::move(*optEvent); const DragEvent& dragEvent = *event; EXPECT_EQ(eventId, dragEvent.getId()); @@ -659,8 +663,10 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent( status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled); ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK"; - std::optional event = mTouchModeEvents.popWithTimeout(TIMEOUT); - ASSERT_NE(std::nullopt, event); + std::optional> optEvent = + mTouchModeEvents.popWithTimeout(TIMEOUT); + ASSERT_TRUE(optEvent.has_value()); + std::unique_ptr event = std::move(*optEvent); const TouchModeEvent& touchModeEvent = *event; EXPECT_EQ(eventId, touchModeEvent.getId()); -- GitLab From 99280991b707a51e575c11b606426d4bc96de7f2 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Mon, 4 Mar 2024 22:49:22 +0000 Subject: [PATCH 088/465] Add surface_control_input_receiver native API Bug: 324271765 Test: ASurfaceControlInputReceiverTest Change-Id: I19a1c796c1d22e8b247368df07c34c1b4b195e64 --- .../android/surface_control_input_receiver.h | 194 ++++++++++++++++++ libs/gui/Choreographer.cpp | 4 + libs/gui/include/gui/Choreographer.h | 1 + 3 files changed, 199 insertions(+) create mode 100644 include/android/surface_control_input_receiver.h diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h new file mode 100644 index 0000000000..cd2c5dfbcf --- /dev/null +++ b/include/android/surface_control_input_receiver.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include +#include +#include + +__BEGIN_DECLS + +/** + * The AInputReceiver_onMotionEvent callback is invoked when the registered input channel receives + * a motion event. + * + * \param context Optional context provided by the client that is passed when creating the + * AInputReceiverCallbacks. + * + * \param motionEvent The motion event. This must be released with AInputEvent_release. + * + * Available since API level 35. + */ +typedef bool (*AInputReceiver_onMotionEvent)(void *_Null_unspecified context, + AInputEvent *_Nonnull motionEvent) + __INTRODUCED_IN(__ANDROID_API_V__); +/** + * The AInputReceiver_onKeyEvent callback is invoked when the registered input channel receives + * a key event. + * + * \param context Optional context provided by the client that is passed when creating the + * AInputReceiverCallbacks. + * + * \param keyEvent The key event. This must be released with AInputEvent_release. + * + * Available since API level 35. + */ +typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context, + AInputEvent *_Nonnull keyEvent) + __INTRODUCED_IN(__ANDROID_API_V__); + +struct AInputReceiverCallbacks; + +struct AInputReceiver; + +/** + * The InputReceiver that holds the reference to the registered input channel. This must be released + * using AInputReceiver_release + * + * Available since API level 35. + */ +typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Registers an input receiver for an ASurfaceControl that will receive batched input event. For + * those events that are batched, the invocation will happen once per AChoreographer frame, and + * other input events will be delivered immediately. + * + * This is different from AInputReceiver_createUnbatchedInputReceiver in that the input events are + * received batched. The caller must invoke AInputReceiver_release to cleanv up the resources when + * no longer needing to use the input receiver. + * + * \param aChoreographer The AChoreographer used for batching. This should match the + * rendering AChoreographer. + * \param hostInputTransferToken The host token to link the embedded. This is used to handle + * transferring touch gesture from host to embedded and for ANRs + * to ensure the host receives the ANR if any issues with + * touch on the embedded. This can be retrieved for the host window + * by calling AttachedSurfaceControl#getInputTransferToken() + * \param aSurfaceControl The ASurfaceControl to register the InputChannel for + * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events + * + * Returns the reference to AInputReceiver to clean up resources when done. + * + * Available since API level 35. + */ +AInputReceiver* _Nonnull +AInputReceiver_createBatchedInputReceiver(AChoreographer* _Nonnull aChoreographer, + const AInputTransferToken* _Nonnull hostInputTransferToken, + const ASurfaceControl* _Nonnull aSurfaceControl, + AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Registers an input receiver for an ASurfaceControl that will receive every input event. + * This is different from AInputReceiver_createBatchedInputReceiver in that the input events are + * received unbatched. The caller must invoke AInputReceiver_release to clean up the resources when + * no longer needing to use the input receiver. + * + * \param aLooper The looper to use when invoking callbacks. + * \param hostInputTransferToken The host token to link the embedded. This is used to handle + * transferring touch gesture from host to embedded and for ANRs + * to ensure the host receives the ANR if any issues with + * touch on the embedded. This can be retrieved for the host window + * by calling AttachedSurfaceControl#getInputTransferToken() + * \param aSurfaceControl The ASurfaceControl to register the InputChannel for + * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events + * + * Returns the reference to AInputReceiver to clean up resources when done. + * + * Available since API level 35. + */ +AInputReceiver* _Nonnull +AInputReceiver_createUnbatchedInputReceiver(ALooper* _Nonnull aLooper, + const AInputTransferToken* _Nonnull hostInputTransferToken, + const ASurfaceControl* _Nonnull aSurfaceControl, + AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Returns the AInputTransferToken that can be used to transfer touch gesture to or from other + * windows. This InputTransferToken is associated with the SurfaceControl that registered an input + * receiver and can be used with the host token for things like transfer touch gesture via + * WindowManager#transferTouchGesture(). + * + * This must be released with AInputTransferToken_release. + * + * \param aInputReceiver The inputReceiver object to retrieve the AInputTransferToken for. + * + * Available since API level 35. + */ +const AInputTransferToken *_Nonnull +AInputReceiver_getInputTransferToken(AInputReceiver *_Nonnull aInputReceiver) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Unregisters the input channel and deletes the AInputReceiver. This must be called on the same + * looper thread it was created with. + * + * \param aInputReceiver The inputReceiver object to release. + * + * Available since API level 35. + */ +void +AInputReceiver_release(AInputReceiver *_Nonnull aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Creates a AInputReceiverCallbacks object that is used when registering for an AInputReceiver. + * This must be released using AInputReceiverCallbacks_release + * + * \param context Optional context provided by the client that will be passed into the callbacks. + * + * Available since API level 35. + */ +AInputReceiverCallbacks* _Nonnull AInputReceiverCallbacks_create(void* _Nullable context) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Releases the AInputReceiverCallbacks. This must be called on the same + * looper thread the AInputReceiver was created with. The receiver will not invoke any callbacks + * once it's been released. + * + * Available since API level 35 + */ +void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nonnull callbacks) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Sets a AInputReceiver_onMotionEvent callback for an AInputReceiverCallbacks + * + * \param callbacks The callback object to set the motion event on. + * \param onMotionEvent The motion event that will be invoked + * + * Available since API level 35. + */ +void AInputReceiverCallbacks_setMotionEventCallback(AInputReceiverCallbacks* _Nonnull callbacks, + AInputReceiver_onMotionEvent _Nonnull onMotionEvent) + __INTRODUCED_IN(__ANDROID_API_V__); + +/** + * Sets a AInputReceiver_onKeyEvent callback for an AInputReceiverCallbacks + * + * \param callbacks The callback object to set the motion event on. + * \param onMotionEvent The key event that will be invoked + * + * Available since API level 35. + */ +void AInputReceiverCallbacks_setKeyEventCallback(AInputReceiverCallbacks* _Nonnull callbacks, + AInputReceiver_onKeyEvent _Nonnull onKeyEvent) + __INTRODUCED_IN(__ANDROID_API_V__); + +__END_DECLS diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp index 54290cd629..0c8f3fa096 100644 --- a/libs/gui/Choreographer.cpp +++ b/libs/gui/Choreographer.cpp @@ -425,4 +425,8 @@ int64_t Choreographer::getStartTimeNanosForVsyncId(AVsyncId vsyncId) { return iter->second; } +const sp Choreographer::getLooper() { + return mLooper; +} + } // namespace android diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h index fc79b03c23..2e5aa4a893 100644 --- a/libs/gui/include/gui/Choreographer.h +++ b/libs/gui/include/gui/Choreographer.h @@ -109,6 +109,7 @@ public: virtual ~Choreographer() override EXCLUDES(gChoreographers.lock); int64_t getFrameInterval() const; bool inCallback() const; + const sp getLooper(); private: Choreographer(const Choreographer&) = delete; -- GitLab From 71fa6fdaf523ce0a3aae7e638f3b5e8f019d2e9b Mon Sep 17 00:00:00 2001 From: Manali Bhutiyani Date: Fri, 15 Mar 2024 19:32:20 +0000 Subject: [PATCH 089/465] [hwc-batching] Add option to override feature flag locally For testing purposes, adding a way to switch flag on/off. Bug: 290685621 Test: Ran following commands and checked if createLayer or the batched version is called. % adb shell dumpsys SurfaceFlinger|grep -C25 "FlagManager values" enable_layer_command_batching: true % adb shell stop && adb shell setprop debug.sf.enable_layer_command_batching false && adb shell start % adb shell dumpsys SurfaceFlinger|grep -C25 "FlagManager values" enable_layer_command_batching: false Change-Id: I47ac3859b35e41b6dcb31c23224692b8d75e8218 --- services/surfaceflinger/common/FlagManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 3b669c6b54..3c2ccbc262 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -210,7 +210,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "") FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "") FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target") FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "") -FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "") +FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching") FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation") FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan") FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "") -- GitLab From 83483148c38b18110a4beec5dd554e9beed74ccb Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Mon, 18 Mar 2024 17:54:36 +0000 Subject: [PATCH 090/465] Revert "Fix check for whether a trace tag is enabled" Revert submission 26575765-is_tag_enabled Reason for revert: Droid-monitored triggered revert due to breakage in b/330172681. Will be verifying through ABTD before submission. Reverted changes: /q/submissionid:26575765-is_tag_enabled Change-Id: I870c6e3edaada40e44ce843cb343619f73b1a42d --- libs/tracing_perfetto/include/tracing_perfetto.h | 2 +- libs/tracing_perfetto/tracing_perfetto.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h index 2c1c2a49e7..4e3c83fca3 100644 --- a/libs/tracing_perfetto/include/tracing_perfetto.h +++ b/libs/tracing_perfetto/include/tracing_perfetto.h @@ -46,7 +46,7 @@ Result traceInstantForTrack(uint64_t category, const char* trackName, Result traceCounter(uint64_t category, const char* name, int64_t value); -bool isTagEnabled(uint64_t category); +uint64_t getEnabledCategories(); } // namespace tracing_perfetto diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index 6f716eea9a..19d1eb639e 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -130,13 +130,12 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } } -bool isTagEnabled(uint64_t category) { - struct PerfettoTeCategory* perfettoTeCategory = - internal::toPerfettoCategory(category); - if (perfettoTeCategory != nullptr) { - return true; +uint64_t getEnabledCategories() { + if (internal::isPerfettoRegistered()) { + // TODO(b/303199244): Return only enabled categories and not all registered ones + return internal::getDefaultCategories(); } else { - return (atrace_get_enabled_tags() & category) != 0; + return atrace_get_enabled_tags(); } } -- GitLab From af854add2313fc931a7bf6be9eb2f5cfbc3e448e Mon Sep 17 00:00:00 2001 From: Ying Wei Date: Sat, 16 Mar 2024 05:24:39 +0000 Subject: [PATCH 091/465] Correct SF frame interval. When SF fps doesn't match vsync rate (either due to vrr or frame rate override), the frame interval calculation in Scheduler should take this into account. Bug: 328352850 Test: atest CtsSurfaceControlTests Test: atest libsurfaceflinger_unittest Test: atest FrameRateOverrideTest Change-Id: I3261c7f2245fcb64c3e79bb3dcb21f1b0e44a395 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 7 +++++-- services/surfaceflinger/Scheduler/Scheduler.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index d92edb81e0..326681a186 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -306,8 +306,11 @@ Period Scheduler::getVsyncPeriod(uid_t uid) { const auto pacesetterOpt = pacesetterDisplayLocked(); LOG_ALWAYS_FATAL_IF(!pacesetterOpt); const Display& pacesetter = *pacesetterOpt; - return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps, - pacesetter.schedulePtr->period()); + const FrameRateMode& frameRateMode = pacesetter.selectorPtr->getActiveMode(); + const auto refreshRate = frameRateMode.fps; + const auto displayVsync = frameRateMode.modePtr->getVsyncRate(); + const auto numPeriod = RefreshRateSelector::getFrameRateDivisor(displayVsync, refreshRate); + return std::make_pair(refreshRate, numPeriod * pacesetter.schedulePtr->period()); }(); const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod(); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 494a91bf21..2cc18e47d3 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -437,6 +437,7 @@ private: // IEventThreadCallback overrides bool throttleVsync(TimePoint, uid_t) override; + // Get frame interval Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock); void resync() override EXCLUDES(mDisplayLock); void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock); -- GitLab From 4c977a4ccaad3203929fb59815bebc4fe1cbfb5f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 15 Mar 2024 16:47:37 +0000 Subject: [PATCH 092/465] PointerChoreographer: Add drawing tablet support PointerChoreographer does not yet support drawing tablets. Add support by creating a MousePointerController for each drawing tablet that is connected. Use a MousePointerController so that the default icon is the same as for a mouse (arrow) instead of for a stylus (no icon). The icon will only show while the stylus is hovering or touching. Bug: 329849066 Test: atest inputflinger_tests Change-Id: I0cbb4a8efc8fd5e50ddfdad2b60df0195a3dce16 --- .../inputflinger/PointerChoreographer.cpp | 130 +++++-- services/inputflinger/PointerChoreographer.h | 3 + .../tests/PointerChoreographer_test.cpp | 351 ++++++++++-------- 3 files changed, 309 insertions(+), 175 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index c333814078..1aa1077d54 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -37,6 +37,11 @@ bool isFromTouchpad(const NotifyMotionArgs& args) { args.pointerProperties[0].toolType == ToolType::FINGER; } +bool isFromDrawingTablet(const NotifyMotionArgs& args) { + return isFromSource(args.source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) && + isStylusToolType(args.pointerProperties[0].toolType); +} + bool isHoverAction(int32_t action) { return action == AMOTION_EVENT_ACTION_HOVER_ENTER || action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT; @@ -46,6 +51,13 @@ bool isStylusHoverEvent(const NotifyMotionArgs& args) { return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action); } +bool isMouseOrTouchpad(uint32_t sources) { + // Check if this is a mouse or touchpad, but not a drawing tablet. + return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) || + (isFromSource(sources, AINPUT_SOURCE_MOUSE) && + !isFromSource(sources, AINPUT_SOURCE_STYLUS)); +} + inline void notifyPointerDisplayChange(std::optional> change, PointerChoreographerPolicyInterface& policy) { if (!change) { @@ -55,6 +67,18 @@ inline void notifyPointerDisplayChange(std::optional, PointerIconStyle>& icon, + PointerControllerInterface& controller) { + if (std::holds_alternative>(icon)) { + if (std::get>(icon) == nullptr) { + LOG(FATAL) << "SpriteIcon should not be null"; + } + controller.setCustomPointerIcon(*std::get>(icon)); + } else { + controller.updatePointerIcon(std::get(icon)); + } +} + } // namespace // --- PointerChoreographer --- @@ -107,6 +131,8 @@ NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& arg return processMouseEventLocked(args); } else if (isFromTouchpad(args)) { return processTouchpadEventLocked(args); + } else if (isFromDrawingTablet(args)) { + processDrawingTabletEventLocked(args); } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) { processStylusHoverEventLocked(args); } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) { @@ -189,6 +215,36 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo return newArgs; } +void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) { + if (args.displayId == ADISPLAY_ID_NONE) { + return; + } + + if (args.getPointerCount() != 1) { + LOG(WARNING) << "Only drawing tablet events with a single pointer are currently supported: " + << args.dump(); + } + + // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist. + auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId, + getMouseControllerConstructor( + args.displayId)); + + PointerControllerInterface& pc = *it->second; + + const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X); + const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y); + pc.setPosition(x, y); + if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) { + // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed + // immediately by a DOWN event. + pc.fade(PointerControllerInterface::Transition::IMMEDIATE); + pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED); + } else if (canUnfadeOnDisplay(args.displayId)) { + pc.unfade(PointerControllerInterface::Transition::IMMEDIATE); + } +} + /** * When screen is touched, fade the mouse pointer on that display. We only call fade for * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the @@ -255,6 +311,8 @@ void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y); pc.setPosition(x, y); if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) { + // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed + // immediately by a DOWN event. pc.fade(PointerControllerInterface::Transition::IMMEDIATE); pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED); } else if (canUnfadeOnDisplay(args.displayId)) { @@ -284,6 +342,7 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) std::scoped_lock _l(mLock); mTouchPointersByDevice.erase(args.deviceId); mStylusPointersByDevice.erase(args.deviceId); + mDrawingTabletPointersByDevice.erase(args.deviceId); } void PointerChoreographer::notifyPointerCaptureChanged( @@ -320,6 +379,11 @@ void PointerChoreographer::dump(std::string& dump) { std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT); dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump; } + dump += INDENT "DrawingTabletControllers:\n"; + for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) { + std::string pointerControllerDump = addLinePrefix(drawingTabletController->dump(), INDENT); + dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump; + } dump += "\n"; } @@ -361,13 +425,13 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo std::set mouseDisplaysToKeep; std::set touchDevicesToKeep; std::set stylusDevicesToKeep; + std::set drawingTabletDevicesToKeep; // Mark the displayIds or deviceIds of PointerControllers currently needed, and create // new PointerControllers if necessary. for (const auto& info : mInputDeviceInfos) { const uint32_t sources = info.getSources(); - if (isFromSource(sources, AINPUT_SOURCE_MOUSE) || - isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) { + if (isMouseOrTouchpad(sources)) { const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId()); mouseDisplaysToKeep.insert(displayId); // For mice, show the cursor immediately when the device is first connected or @@ -388,6 +452,10 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { stylusDevicesToKeep.insert(info.getId()); } + if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) && + info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { + drawingTabletDevicesToKeep.insert(info.getId()); + } } // Remove PointerControllers no longer needed. @@ -400,6 +468,9 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) { return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end(); }); + std::erase_if(mDrawingTabletPointersByDevice, [&drawingTabletDevicesToKeep](const auto& pair) { + return drawingTabletDevicesToKeep.find(pair.first) == drawingTabletDevicesToKeep.end(); + }); std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) { return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(), [id](const auto& info) { return info.getId() == id; }) == @@ -460,6 +531,12 @@ void PointerChoreographer::setDisplayViewports(const std::vectorsetDisplayViewport(viewport); } } + for (const auto& [deviceId, drawingTabletController] : mDrawingTabletPointersByDevice) { + const InputDeviceInfo* info = findInputDeviceLocked(deviceId); + if (info && info->getAssociatedDisplayId() == displayId) { + drawingTabletController->setDisplayViewport(viewport); + } + } } mViewports = viewports; pointerDisplayChange = calculatePointerDisplayChangeToNotify(); @@ -533,42 +610,35 @@ bool PointerChoreographer::setPointerIcon( return false; } const uint32_t sources = info->getSources(); - const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId); - if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && - stylusPointerIt != mStylusPointersByDevice.end()) { - if (std::holds_alternative>(icon)) { - if (std::get>(icon) == nullptr) { - LOG(FATAL) << "SpriteIcon should not be null"; - } - stylusPointerIt->second->setCustomPointerIcon( - *std::get>(icon)); - } else { - stylusPointerIt->second->updatePointerIcon(std::get(icon)); + if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE)) { + auto it = mDrawingTabletPointersByDevice.find(deviceId); + if (it != mDrawingTabletPointersByDevice.end()) { + setIconForController(icon, *it->second); + return true; } - } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) { - if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId); - mousePointerIt != mMousePointersByDisplay.end()) { - if (std::holds_alternative>(icon)) { - if (std::get>(icon) == nullptr) { - LOG(FATAL) << "SpriteIcon should not be null"; - } - mousePointerIt->second->setCustomPointerIcon( - *std::get>(icon)); - } else { - mousePointerIt->second->updatePointerIcon(std::get(icon)); - } + } + if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) { + auto it = mStylusPointersByDevice.find(deviceId); + if (it != mStylusPointersByDevice.end()) { + setIconForController(icon, *it->second); + return true; + } + } + if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) { + auto it = mMousePointersByDisplay.find(displayId); + if (it != mMousePointersByDisplay.end()) { + setIconForController(icon, *it->second); + return true; } else { LOG(WARNING) << "No mouse pointer controller found for display " << displayId << ", device " << deviceId << "."; return false; } - } else { - LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device " - << deviceId << "."; - return false; } - return true; + LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device " << deviceId + << "."; + return false; } void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) { diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index db1488b546..a3c210e696 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -123,6 +123,7 @@ private: NotifyMotionArgs processMotion(const NotifyMotionArgs& args); NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); + void processDrawingTabletEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processDeviceReset(const NotifyDeviceResetArgs& args); @@ -144,6 +145,8 @@ private: GUARDED_BY(mLock); std::map> mStylusPointersByDevice GUARDED_BY(mLock); + std::map> mDrawingTabletPointersByDevice + GUARDED_BY(mLock); int32_t mDefaultMouseDisplayId GUARDED_BY(mLock); int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock); diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 8ddb672cfe..3b2565e973 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -46,6 +46,7 @@ constexpr int32_t DISPLAY_ID = 5; constexpr int32_t ANOTHER_DISPLAY_ID = 10; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; +constexpr auto DRAWING_TABLET_SOURCE = AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS; const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE) .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) @@ -759,12 +760,28 @@ TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) { assertPointerControllerRemoved(pc); } -TEST_F(PointerChoreographerTest, - WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) { +using StylusFixtureParam = + std::tuple; + +class StylusTestFixture : public PointerChoreographerTest, + public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P(PointerChoreographerTest, StylusTestFixture, + ::testing::Values(std::make_tuple("DirectStylus", AINPUT_SOURCE_STYLUS, + ControllerType::STYLUS), + std::make_tuple("DrawingTablet", DRAWING_TABLET_SOURCE, + ControllerType::MOUSE)), + [](const testing::TestParamInfo& p) { + return std::string{std::get<0>(p.param)}; + }); + +TEST_P(StylusTestFixture, WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) { + const auto& [name, source, controllerType] = GetParam(); + // Disable stylus pointer icon and add a stylus device. mChoreographer.setStylusPointerIconEnabled(false); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); assertPointerControllerNotCreated(); // Enable stylus pointer icon. PointerController still should not be created. @@ -772,25 +789,25 @@ TEST_F(PointerChoreographerTest, assertPointerControllerNotCreated(); } -TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) { +TEST_P(StylusTestFixture, WhenStylusHoverEventOccursCreatesPointerController) { + const auto& [name, source, controllerType] = GetParam(); + // Add a stylus device and enable stylus pointer icon. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); assertPointerControllerNotCreated(); // Emit hover event. Now PointerController should be created. - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + assertPointerControllerCreated(controllerType); } -TEST_F(PointerChoreographerTest, - WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) { +TEST_F(PointerChoreographerTest, StylusHoverEventWhenStylusPointerIconDisabled) { // Add a stylus device and disable stylus pointer icon. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); @@ -807,25 +824,43 @@ TEST_F(PointerChoreographerTest, assertPointerControllerNotCreated(); } -TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) { - // Make sure the PointerController is created. +TEST_F(PointerChoreographerTest, DrawingTabletHoverEventWhenStylusPointerIconDisabled) { + // Add a drawing tablet and disable stylus pointer icon. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); - mChoreographer.setStylusPointerIconEnabled(true); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(false); + assertPointerControllerNotCreated(); + + // Emit hover event. Drawing tablets are not affected by "stylus pointer icon" setting. mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE) .pointer(STYLUS_POINTER) .deviceId(DEVICE_ID) .displayId(DISPLAY_ID) .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + assertPointerControllerCreated(ControllerType::MOUSE); +} + +TEST_P(StylusTestFixture, WhenStylusDeviceIsRemovedRemovesPointerController) { + const auto& [name, source, controllerType] = GetParam(); + + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); // Remove the device. mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); assertPointerControllerRemoved(pc); } -TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) { +TEST_F(PointerChoreographerTest, StylusPointerIconDisabledRemovesPointerController) { // Make sure the PointerController is created. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); @@ -843,38 +878,59 @@ TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerCont assertPointerControllerRemoved(pc); } -TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) { - // Set viewport. - mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); - +TEST_F(PointerChoreographerTest, + StylusPointerIconDisabledDoesNotRemoveDrawingTabletPointerController) { // Make sure the PointerController is created. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE) .pointer(STYLUS_POINTER) .deviceId(DEVICE_ID) .displayId(DISPLAY_ID) .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + + // Disable stylus pointer icon. This should not affect drawing tablets. + mChoreographer.setStylusPointerIconEnabled(false); + assertPointerControllerNotRemoved(pc); +} + +TEST_P(StylusTestFixture, SetsViewportForStylusPointerController) { + const auto& [name, source, controllerType] = GetParam(); + + // Set viewport. + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Make sure the PointerController is created. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); + mChoreographer.setStylusPointerIconEnabled(true); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); // Check that viewport is set for the PointerController. pc->assertViewportSet(DISPLAY_ID); } -TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) { +TEST_P(StylusTestFixture, WhenViewportIsSetLaterSetsViewportForStylusPointerController) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure the PointerController is created. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); // Check that viewport is unset. pc->assertViewportNotSet(); @@ -886,19 +942,19 @@ TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPoin pc->assertViewportSet(DISPLAY_ID); } -TEST_F(PointerChoreographerTest, - WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) { +TEST_P(StylusTestFixture, WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure the PointerController is created. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); // Check that viewport is unset. pc->assertViewportNotSet(); @@ -910,24 +966,25 @@ TEST_F(PointerChoreographerTest, pc->assertViewportNotSet(); } -TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) { +TEST_P(StylusTestFixture, StylusHoverManipulatesPointer) { + const auto& [name, source, controllerType] = GetParam(); + mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); // Emit hover enter event. This is for creating PointerController. - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); // Emit hover move event. After bounds are set, PointerController will update the position. mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source) .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250)) .deviceId(DEVICE_ID) .displayId(DISPLAY_ID) @@ -937,7 +994,7 @@ TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) { // Emit hover exit event. mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source) .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250)) .deviceId(DEVICE_ID) .displayId(DISPLAY_ID) @@ -946,38 +1003,38 @@ TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) { ASSERT_FALSE(pc->isPointerShown()); } -TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) { +TEST_P(StylusTestFixture, StylusHoverManipulatesPointerForTwoDisplays) { + const auto& [name, source, controllerType] = GetParam(); + mChoreographer.setStylusPointerIconEnabled(true); // Add two stylus devices associated to different displays. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID), - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}}); + {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID), + generateTestDeviceInfo(SECOND_DEVICE_ID, source, ANOTHER_DISPLAY_ID)}}); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); // Emit hover event with first device. This is for creating PointerController. - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto firstDisplayPc = assertPointerControllerCreated(controllerType); // Emit hover event with second device. This is for creating PointerController. - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(SECOND_DEVICE_ID) - .displayId(ANOTHER_DISPLAY_ID) - .build()); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(ANOTHER_DISPLAY_ID) + .build()); // There should be another PointerController created. - auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS); + auto secondDisplayPc = assertPointerControllerCreated(controllerType); // Emit hover event with first device. mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source) .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250)) .deviceId(DEVICE_ID) .displayId(DISPLAY_ID) @@ -989,7 +1046,7 @@ TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) { // Emit hover event with second device. mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS) + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, source) .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350)) .deviceId(SECOND_DEVICE_ID) .displayId(ANOTHER_DISPLAY_ID) @@ -1004,19 +1061,20 @@ TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) { ASSERT_TRUE(firstDisplayPc->isPointerShown()); } -TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) { +TEST_P(StylusTestFixture, WhenStylusDeviceIsResetRemovesPointer) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure the PointerController is created and there is a pointer. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); ASSERT_TRUE(pc->isPointerShown()); // Reset the device and see the pointer controller was removed. @@ -1460,19 +1518,20 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) { firstMousePc->assertPointerIconNotSet(); } -TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) { +TEST_P(StylusTestFixture, SetsPointerIconForStylus) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure there is a PointerController. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); pc->assertPointerIconNotSet(); // Set pointer icon for the device. @@ -1485,28 +1544,28 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) { pc->assertPointerIconNotSet(); // The stylus stops hovering. This should cause the icon to be reset. - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); pc->assertPointerIconSet(PointerIconStyle::TYPE_NOT_SPECIFIED); } -TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) { +TEST_P(StylusTestFixture, SetsCustomPointerIconForStylus) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure there is a PointerController. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto pc = assertPointerControllerCreated(controllerType); pc->assertCustomPointerIconNotSet(); // Set custom pointer icon for the device. @@ -1522,28 +1581,28 @@ TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) { pc->assertCustomPointerIconNotSet(); } -TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) { +TEST_P(StylusTestFixture, SetsPointerIconForTwoStyluses) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure there are two StylusPointerControllers. They can be on a same display. mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID), - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID), + generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(SECOND_DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto firstStylusPc = assertPointerControllerCreated(controllerType); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto secondStylusPc = assertPointerControllerCreated(controllerType); // Set pointer icon for one stylus. ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID)); @@ -1557,14 +1616,16 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) { firstStylusPc->assertPointerIconNotSet(); } -TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) { +TEST_P(StylusTestFixture, SetsPointerIconForMouseAndStylus) { + const auto& [name, source, controllerType] = GetParam(); + // Make sure there are PointerControllers for a mouse and a stylus. mChoreographer.setStylusPointerIconEnabled(true); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); + generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyMotion( MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) @@ -1573,13 +1634,12 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) { .displayId(ADISPLAY_ID_NONE) .build()); auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(SECOND_DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(SECOND_DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + auto stylusPc = assertPointerControllerCreated(controllerType); // Set pointer icon for the mouse. ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID)); @@ -1688,7 +1748,9 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForTouchpad ASSERT_FALSE(touchpadPc->isPointerShown()); } -TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForStylus) { +TEST_P(StylusTestFixture, SetPointerIconVisibilityHidesPointerForStylus) { + const auto& [name, source, controllerType] = GetParam(); + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setStylusPointerIconEnabled(true); @@ -1696,15 +1758,14 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForStylus) mChoreographer.setPointerIconVisibility(DISPLAY_ID, false); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) - .pointer(STYLUS_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) + .pointer(STYLUS_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID)); - auto pc = assertPointerControllerCreated(ControllerType::STYLUS); + auto pc = assertPointerControllerCreated(controllerType); pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT); // The pointer should not be visible. -- GitLab From f49bc7438d79a10e5a1800b1e147ecffb22935e1 Mon Sep 17 00:00:00 2001 From: Cody Heiner Date: Mon, 11 Mar 2024 17:47:19 -0700 Subject: [PATCH 093/465] Stylus Metrics: Switch to non-bootstrap statslog library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that libinput is no longer run in a bootstrap process, we can switch the stats logging library to the standard version. This version is host supported, so we now also remove the `#ifdef __ANDROID__` include guards. Test: m checkinput Test: flash to device, run `statsd_testdrive 718`, then trigger stylus prediction → reported metrics are present and reasonable Bug: 311066949 Change-Id: I04e6f6906e556f4f5a71b711c6ef29d8aa6f3501 --- libs/input/Android.bp | 49 +------------------- libs/input/MotionPredictorMetricsManager.cpp | 33 ++++++------- libs/input/tests/Android.bp | 9 +--- 3 files changed, 15 insertions(+), 76 deletions(-) diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 3278c23b6f..5e38bdc493 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -248,6 +248,7 @@ cc_library { "libcutils", "liblog", "libPlatformProperties", + "libstatslog", "libtinyxml2", "libutils", "libz", // needed by libkernelconfigs @@ -288,17 +289,6 @@ cc_library { target: { android: { - export_shared_lib_headers: ["libbinder"], - - shared_libs: [ - "libutils", - "libbinder", - // Stats logging library and its dependencies. - "libstatslog_libinput", - "libstatsbootstrap", - "android.os.statsbootstrap_aidl-cpp", - ], - required: [ "motion_predictor_model_prebuilt", "motion_predictor_model_config", @@ -313,43 +303,6 @@ cc_library { }, } -// Use bootstrap version of stats logging library. -// libinput is a bootstrap process (starts early in the boot process), and thus can't use the normal -// `libstatslog` because that requires `libstatssocket`, which is only available later in the boot. -cc_library { - name: "libstatslog_libinput", - generated_sources: ["statslog_libinput.cpp"], - generated_headers: ["statslog_libinput.h"], - export_generated_headers: ["statslog_libinput.h"], - shared_libs: [ - "libbinder", - "libstatsbootstrap", - "libutils", - "android.os.statsbootstrap_aidl-cpp", - ], -} - -genrule { - name: "statslog_libinput.h", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h --module libinput" + - " --namespace android,stats,libinput --bootstrap", - out: [ - "statslog_libinput.h", - ], -} - -genrule { - name: "statslog_libinput.cpp", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp --module libinput" + - " --namespace android,stats,libinput --importHeader statslog_libinput.h" + - " --bootstrap", - out: [ - "statslog_libinput.cpp", - ], -} - cc_defaults { name: "libinput_fuzz_defaults", cpp_std: "c++20", diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp index 6872af2aa5..149a36ee31 100644 --- a/libs/input/MotionPredictorMetricsManager.cpp +++ b/libs/input/MotionPredictorMetricsManager.cpp @@ -21,14 +21,11 @@ #include #include +#include #include "Eigen/Core" #include "Eigen/Geometry" -#ifdef __ANDROID__ -#include -#endif - namespace android { namespace { @@ -48,22 +45,18 @@ inline constexpr float PATH_LENGTH_EPSILON = 0.001; void MotionPredictorMetricsManager::defaultReportAtomFunction( const MotionPredictorMetricsManager::AtomFields& atomFields) { - // Call stats_write logging function only on Android targets (not supported on host). -#ifdef __ANDROID__ - android::stats::libinput:: - stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED, - /*stylus_vendor_id=*/0, - /*stylus_product_id=*/0, - atomFields.deltaTimeBucketMilliseconds, - atomFields.alongTrajectoryErrorMeanMillipixels, - atomFields.alongTrajectoryErrorStdMillipixels, - atomFields.offTrajectoryRmseMillipixels, - atomFields.pressureRmseMilliunits, - atomFields.highVelocityAlongTrajectoryRmse, - atomFields.highVelocityOffTrajectoryRmse, - atomFields.scaleInvariantAlongTrajectoryRmse, - atomFields.scaleInvariantOffTrajectoryRmse); -#endif + android::util::stats_write(android::util::STYLUS_PREDICTION_METRICS_REPORTED, + /*stylus_vendor_id=*/0, + /*stylus_product_id=*/0, + atomFields.deltaTimeBucketMilliseconds, + atomFields.alongTrajectoryErrorMeanMillipixels, + atomFields.alongTrajectoryErrorStdMillipixels, + atomFields.offTrajectoryRmseMillipixels, + atomFields.pressureRmseMilliunits, + atomFields.highVelocityAlongTrajectoryRmse, + atomFields.highVelocityOffTrajectoryRmse, + atomFields.scaleInvariantAlongTrajectoryRmse, + atomFields.scaleInvariantOffTrajectoryRmse); } MotionPredictorMetricsManager::MotionPredictorMetricsManager( diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index e67a65a114..1144f4d861 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -64,6 +64,7 @@ cc_test { "libcutils", "liblog", "libPlatformProperties", + "libstatslog", "libtinyxml2", "libutils", "server_configurable_flags", @@ -82,14 +83,6 @@ cc_test { address: true, }, }, - android: { - static_libs: [ - // Stats logging library and its dependencies. - "libstatslog_libinput", - "libstatsbootstrap", - "android.os.statsbootstrap_aidl-cpp", - ], - }, }, } -- GitLab From 47504789e2d22b4ef5d98700992b0243e0cc8922 Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Fri, 8 Mar 2024 23:28:32 +0000 Subject: [PATCH 094/465] libgui: avoid allocations for bit manip Avoid doing allocations when getting layer metadata attributes. Bug: 328177618 Test: TH Change-Id: I63189bce152bd275bd46d9d2cdd4d7d02d282533 --- libs/gui/LayerMetadata.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp index 4e12fd330c..535a0218b6 100644 --- a/libs/gui/LayerMetadata.cpp +++ b/libs/gui/LayerMetadata.cpp @@ -100,27 +100,31 @@ bool LayerMetadata::has(uint32_t key) const { int32_t LayerMetadata::getInt32(uint32_t key, int32_t fallback) const { if (!has(key)) return fallback; const std::vector& data = mMap.at(key); - if (data.size() < sizeof(uint32_t)) return fallback; - Parcel p; - p.setData(data.data(), data.size()); - return p.readInt32(); + + // TODO: should handle when not equal? + if (data.size() < sizeof(int32_t)) return fallback; + + int32_t result; + memcpy(&result, data.data(), sizeof(result)); + return result; } void LayerMetadata::setInt32(uint32_t key, int32_t value) { std::vector& data = mMap[key]; - Parcel p; - p.writeInt32(value); - data.resize(p.dataSize()); - memcpy(data.data(), p.data(), p.dataSize()); + data.resize(sizeof(value)); + memcpy(data.data(), &value, sizeof(value)); } std::optional LayerMetadata::getInt64(uint32_t key) const { if (!has(key)) return std::nullopt; const std::vector& data = mMap.at(key); + + // TODO: should handle when not equal? if (data.size() < sizeof(int64_t)) return std::nullopt; - Parcel p; - p.setData(data.data(), data.size()); - return p.readInt64(); + + int64_t result; + memcpy(&result, data.data(), sizeof(result)); + return result; } std::string LayerMetadata::itemToString(uint32_t key, const char* separator) const { -- GitLab From 58f19f6802bac3be9897c0db36c0c303b64e3822 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 7 Mar 2024 21:54:34 +0000 Subject: [PATCH 095/465] InputTracer: Read trace configuration from perfetto Bug: 210460522 Test: manual with perfetto Change-Id: I5eba37fd2dbf76f92c91504fed403642761cdb1d --- .../trace/AndroidInputEventProtoConverter.cpp | 78 +++++++++++++++++++ .../trace/AndroidInputEventProtoConverter.h | 4 + .../trace/InputTracingPerfettoBackend.cpp | 23 +++++- .../trace/InputTracingPerfettoBackend.h | 15 +++- .../trace/InputTracingPerfettoBackendConfig.h | 60 ++++++++++++++ 5 files changed, 174 insertions(+), 6 deletions(-) create mode 100644 services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp index cee741cfc7..ddc4cb3851 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp @@ -21,6 +21,23 @@ namespace android::inputdispatcher::trace { +namespace { + +using namespace ftl::flag_operators; + +// The trace config to use for maximal tracing. +const impl::TraceConfig CONFIG_TRACE_ALL{ + .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS | + impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH, + .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE, + .matchAllPackages = {}, + .matchAnyPackages = {}, + .matchSecure{}, + .matchImeConnectionActive = {}}}, +}; + +} // namespace + void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event, proto::AndroidMotionEvent& outProto) { outProto.set_event_id(event.id); @@ -105,4 +122,65 @@ void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent( } } +impl::TraceConfig AndroidInputEventProtoConverter::parseConfig( + proto::AndroidInputEventConfig::Decoder& protoConfig) { + if (protoConfig.has_mode() && + protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) { + // User has requested the preset for maximal tracing + return CONFIG_TRACE_ALL; + } + + impl::TraceConfig config; + + // Parse trace flags + if (protoConfig.has_trace_dispatcher_input_events() && + protoConfig.trace_dispatcher_input_events()) { + config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS; + } + if (protoConfig.has_trace_dispatcher_window_dispatch() && + protoConfig.trace_dispatcher_window_dispatch()) { + config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH; + } + + // Parse trace rules + auto rulesIt = protoConfig.rules(); + while (rulesIt) { + proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()}; + config.rules.emplace_back(); + auto& rule = config.rules.back(); + + rule.level = protoRule.has_trace_level() + ? static_cast(protoRule.trace_level()) + : impl::TraceLevel::TRACE_LEVEL_NONE; + + if (protoRule.has_match_all_packages()) { + auto pkgIt = protoRule.match_all_packages(); + while (pkgIt) { + rule.matchAllPackages.emplace_back(pkgIt->as_std_string()); + pkgIt++; + } + } + + if (protoRule.has_match_any_packages()) { + auto pkgIt = protoRule.match_any_packages(); + while (pkgIt) { + rule.matchAnyPackages.emplace_back(pkgIt->as_std_string()); + pkgIt++; + } + } + + if (protoRule.has_match_secure()) { + rule.matchSecure = protoRule.match_secure(); + } + + if (protoRule.has_match_ime_connection_active()) { + rule.matchImeConnectionActive = protoRule.match_ime_connection_active(); + } + + rulesIt++; + } + + return config; +} + } // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h index ab5f9ca610..bab17010ff 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h @@ -16,9 +16,11 @@ #pragma once +#include #include #include "InputTracingBackendInterface.h" +#include "InputTracingPerfettoBackendConfig.h" namespace proto = perfetto::protos::pbzero; @@ -34,6 +36,8 @@ public: static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto); static void toProtoWindowDispatchEvent(const WindowDispatchArgs&, proto::AndroidWindowInputDispatchEvent& outProto); + + static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig); }; } // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 8ef9ca504d..e8d89e095a 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -33,12 +33,25 @@ constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent"; // --- PerfettoBackend::InputEventDataSource --- -void PerfettoBackend::InputEventDataSource::OnStart(const perfetto::DataSourceBase::StartArgs&) { - LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME; +PerfettoBackend::InputEventDataSource::InputEventDataSource() : mInstanceId(sNextInstanceId++) {} + +void PerfettoBackend::InputEventDataSource::OnSetup(const InputEventDataSource::SetupArgs& args) { + LOG(INFO) << "Setting up perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME + << ", instanceId: " << mInstanceId; + const auto rawConfig = args.config->android_input_event_config_raw(); + auto protoConfig = perfetto::protos::pbzero::AndroidInputEventConfig::Decoder{rawConfig}; + + mConfig = AndroidInputEventProtoConverter::parseConfig(protoConfig); } -void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBase::StopArgs&) { - LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME; +void PerfettoBackend::InputEventDataSource::OnStart(const InputEventDataSource::StartArgs&) { + LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME + << ", instanceId: " << mInstanceId; +} + +void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::StopArgs&) { + LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME + << ", instanceId: " << mInstanceId; InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); }); } @@ -46,6 +59,8 @@ void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBas std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; +std::atomic PerfettoBackend::sNextInstanceId{1}; + PerfettoBackend::PerfettoBackend() { // Use a once-flag to ensure that the data source is only registered once per boot, since // we never unregister the InputEventDataSource. diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index d4553758a4..7e1e36aba0 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -18,6 +18,8 @@ #include "InputTracingBackendInterface.h" +#include "InputTracingPerfettoBackendConfig.h" + #include #include @@ -52,15 +54,24 @@ public: void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override; void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override; +private: + // Implementation of the perfetto data source. + // Each instance of the InputEventDataSource represents a different tracing session. class InputEventDataSource : public perfetto::DataSource { public: - void OnSetup(const SetupArgs&) override {} + explicit InputEventDataSource(); + + void OnSetup(const SetupArgs&) override; void OnStart(const StartArgs&) override; void OnStop(const StopArgs&) override; + + private: + const int32_t mInstanceId; + TraceConfig mConfig; }; -private: static std::once_flag sDataSourceRegistrationFlag; + static std::atomic sNextInstanceId; }; } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h new file mode 100644 index 0000000000..536e32b857 --- /dev/null +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h @@ -0,0 +1,60 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace android::inputdispatcher::trace::impl { + +/** Flags representing the configurations that are enabled in the trace. */ +enum class TraceFlag : uint32_t { + // Trace details about input events processed by InputDispatcher. + TRACE_DISPATCHER_INPUT_EVENTS = 0x1, + // Trace details about an event being sent to a window by InputDispatcher. + TRACE_DISPATCHER_WINDOW_DISPATCH = 0x2, + + ftl_last = TRACE_DISPATCHER_WINDOW_DISPATCH, +}; + +/** Representation of AndroidInputEventConfig::TraceLevel. */ +using TraceLevel = perfetto::protos::pbzero::AndroidInputEventConfig::TraceLevel; + +/** Representation of AndroidInputEventConfig::TraceRule. */ +struct TraceRule { + TraceLevel level; + + std::vector matchAllPackages; + std::vector matchAnyPackages; + std::optional matchSecure; + std::optional matchImeConnectionActive; +}; + +/** + * A complete configuration for a tracing session. + * + * The trace rules are applied as documented in the perfetto config: + * /external/perfetto/protos/perfetto/config/android/android_input_event_config.proto + */ +struct TraceConfig { + ftl::Flags flags; + std::vector rules; +}; + +} // namespace android::inputdispatcher::trace::impl -- GitLab From 4c49aad74a6bb3983a3da32428ea5e9f6b638730 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 8 Feb 2024 20:42:35 +0000 Subject: [PATCH 096/465] InputTracer: Track inbound events Track whether an event is an inbound event or a synthesized event. Then read the config flags from the perfetto backend, and decide whether to ignore events in the trace depending on the flags that are enabled. Bug: 210460522 Test: manual with perfetto Change-Id: I665644f026fca4ab823221656c7b519893e2a1eb --- .../dispatcher/trace/InputTracer.cpp | 51 ++++++++++--------- .../trace/InputTracingBackendInterface.h | 16 ++++++ .../trace/InputTracingPerfettoBackend.cpp | 26 ++++++++++ .../trace/InputTracingPerfettoBackend.h | 4 ++ 4 files changed, 74 insertions(+), 23 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 47e27beff7..d7762493c3 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -30,7 +30,7 @@ struct Visitor : V... { using V::operator()...; }; -TracedEvent createTracedEvent(const MotionEntry& e) { +TracedEvent createTracedEvent(const MotionEntry& e, EventType type) { return TracedMotionEvent{e.id, e.eventTime, e.policyFlags, @@ -50,13 +50,14 @@ TracedEvent createTracedEvent(const MotionEntry& e) { e.yCursorPosition, e.downTime, e.pointerProperties, - e.pointerCoords}; + e.pointerCoords, + type}; } -TracedEvent createTracedEvent(const KeyEntry& e) { +TracedEvent createTracedEvent(const KeyEntry& e, EventType type) { return TracedKeyEvent{e.id, e.eventTime, e.policyFlags, e.deviceId, e.source, e.displayId, e.action, e.keyCode, e.scanCode, e.metaState, - e.downTime, e.flags, e.repeatCount}; + e.downTime, e.flags, e.repeatCount, type}; } void writeEventToBackend(const TracedEvent& event, const TracedEventArgs args, @@ -83,10 +84,10 @@ std::unique_ptr InputTracer::traceInboundEvent(const Even if (entry.type == EventEntry::Type::MOTION) { const auto& motion = static_cast(entry); - eventState->events.emplace_back(createTracedEvent(motion)); + eventState->events.emplace_back(createTracedEvent(motion, EventType::INBOUND)); } else if (entry.type == EventEntry::Type::KEY) { const auto& key = static_cast(entry); - eventState->events.emplace_back(createTracedEvent(key)); + eventState->events.emplace_back(createTracedEvent(key, EventType::INBOUND)); } else { LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); } @@ -138,10 +139,10 @@ std::unique_ptr InputTracer::traceDerivedEvent( if (entry.type == EventEntry::Type::MOTION) { const auto& motion = static_cast(entry); - eventState->events.emplace_back(createTracedEvent(motion)); + eventState->events.emplace_back(createTracedEvent(motion, EventType::SYNTHESIZED)); } else if (entry.type == EventEntry::Type::KEY) { const auto& key = static_cast(entry); - eventState->events.emplace_back(createTracedEvent(key)); + eventState->events.emplace_back(createTracedEvent(key, EventType::SYNTHESIZED)); } else { LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); } @@ -150,8 +151,9 @@ std::unique_ptr InputTracer::traceDerivedEvent( // It is possible for a derived event to be dispatched some time after the original event // is dispatched, such as in the case of key fallback events. To account for these cases, // derived events can be traced after the processing is complete for the original event. + const auto& event = eventState->events.back(); const TracedEventArgs traceArgs{.isSecure = eventState->isSecure}; - writeEventToBackend(eventState->events.back(), traceArgs, *mBackend); + writeEventToBackend(event, traceArgs, *mBackend); } return std::make_unique(std::move(eventState), /*isDerived=*/true); } @@ -160,26 +162,18 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, const EventTrackerInterface& cookie) { auto& eventState = getState(cookie); const EventEntry& entry = *dispatchEntry.eventEntry; + const int32_t eventId = entry.id; // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable. // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced, // so we need to find the repeatCount at the time of dispatching to trace it accurately. int32_t resolvedKeyRepeatCount = 0; - - TracedEvent traced; - if (entry.type == EventEntry::Type::MOTION) { - const auto& motion = static_cast(entry); - traced = createTracedEvent(motion); - } else if (entry.type == EventEntry::Type::KEY) { - const auto& key = static_cast(entry); - resolvedKeyRepeatCount = key.repeatCount; - traced = createTracedEvent(key); - } else { - LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type); + if (entry.type == EventEntry::Type::KEY) { + resolvedKeyRepeatCount = static_cast(entry).repeatCount; } auto tracedEventIt = std::find_if(eventState->events.begin(), eventState->events.end(), - [&traced](const auto& event) { return getId(traced) == getId(event); }); + [eventId](const auto& event) { return eventId == getId(event); }); if (tracedEventIt == eventState->events.end()) { LOG(FATAL) << __func__ @@ -191,7 +185,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0; // TODO(b/210460522): Pass HMAC into traceEventDispatch. - const WindowDispatchArgs windowDispatchArgs{std::move(traced), + const WindowDispatchArgs windowDispatchArgs{*tracedEventIt, dispatchEntry.deliveryTime, dispatchEntry.resolvedFlags, dispatchEntry.targetUid, @@ -222,12 +216,23 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { void InputTracer::EventState::onEventProcessingComplete() { // Write all of the events known so far to the trace. - const TracedEventArgs traceArgs{.isSecure = isSecure}; for (const auto& event : events) { + const TracedEventArgs traceArgs{.isSecure = isSecure}; writeEventToBackend(event, traceArgs, *tracer.mBackend); } // Write all pending dispatch args to the trace. for (const auto& windowDispatchArgs : pendingDispatchArgs) { + auto tracedEventIt = + std::find_if(events.begin(), events.end(), + [id = getId(windowDispatchArgs.eventEntry)](const auto& event) { + return id == getId(event); + }); + if (tracedEventIt == events.end()) { + LOG(FATAL) << __func__ + << ": Failed to find a previously traced event that matches the dispatched " + "event"; + } + const TracedEventArgs traceArgs{.isSecure = isSecure}; tracer.mBackend->traceWindowDispatch(windowDispatchArgs, traceArgs); } pendingDispatchArgs.clear(); diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 6eef12e536..4bb5799d72 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -26,6 +26,20 @@ namespace android::inputdispatcher::trace { +/** + * Describes the type of this event being traced, with respect to InputDispatcher. + */ +enum class EventType { + // This is an event that was reported through the InputListener interface or was injected. + INBOUND, + // This is an event that was synthesized within InputDispatcher; either being derived + // from an inbound event (e.g. a split motion event), or synthesized completely + // (e.g. a CANCEL event generated when the inbound stream is not canceled). + SYNTHESIZED, + + ftl_last = SYNTHESIZED, +}; + /** * A representation of an Android KeyEvent used by the tracing backend. */ @@ -43,6 +57,7 @@ struct TracedKeyEvent { nsecs_t downTime; int32_t flags; int32_t repeatCount; + EventType eventType; }; /** @@ -69,6 +84,7 @@ struct TracedMotionEvent { nsecs_t downTime; std::vector pointerProperties; std::vector pointerCoords; + EventType eventType; }; /** A representation of a traced input event. */ diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index e8d89e095a..998ec8a7c8 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -55,6 +55,20 @@ void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::S InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); }); } +bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent( + const EventType& type) const { + if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS)) { + // Ignore all input events. + return true; + } + if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH) && + type != EventType::INBOUND) { + // When window dispatch tracing is disabled, ignore any events that are not inbound events. + return true; + } + return false; +} + // --- PerfettoBackend --- std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; @@ -85,6 +99,10 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, return; } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { + auto dataSource = ctx.GetDataSourceLocked(); + if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { + return; + } auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); auto* dispatchMotion = inputEvent->set_dispatcher_motion_event(); @@ -98,6 +116,10 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEve return; } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { + auto dataSource = ctx.GetDataSourceLocked(); + if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { + return; + } auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); auto* dispatchKey = inputEvent->set_dispatcher_key_event(); @@ -112,6 +134,10 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs return; } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { + auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) { + return; + } auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); auto* dispatchEvent = inputEvent->set_dispatcher_window_dispatch_event(); diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index 7e1e36aba0..920ea600a9 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -20,6 +20,7 @@ #include "InputTracingPerfettoBackendConfig.h" +#include #include #include @@ -65,6 +66,9 @@ private: void OnStart(const StartArgs&) override; void OnStop(const StopArgs&) override; + bool shouldIgnoreTracedInputEvent(const EventType&) const; + inline ftl::Flags getFlags() const { return mConfig.flags; } + private: const int32_t mInstanceId; TraceConfig mConfig; -- GitLab From b568238e53890fce51eeaab4325db1f627262461 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 23 Feb 2024 22:52:31 +0000 Subject: [PATCH 097/465] InputTracer: Distinguish sensitive events There are three levels of tracing: complete, redacted, and none. Write events that are redacted into a separate proto, while also omitting certain fields from being written. The redacted level will be used in subsequent CLs. Bug: 210460522 Test: manual with perfetto Change-Id: Ie65e7915c3aa5fc972e675c43e46970363bab3a4 --- .../trace/AndroidInputEventProtoConverter.cpp | 34 +++++++--- .../trace/AndroidInputEventProtoConverter.h | 8 ++- .../trace/InputTracingPerfettoBackend.cpp | 68 ++++++++++++++----- .../trace/InputTracingPerfettoBackend.h | 3 + 4 files changed, 83 insertions(+), 30 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp index ddc4cb3851..c431fb726a 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp @@ -39,7 +39,8 @@ const impl::TraceConfig CONFIG_TRACE_ALL{ } // namespace void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event, - proto::AndroidMotionEvent& outProto) { + proto::AndroidMotionEvent& outProto, + bool isRedacted) { outProto.set_event_id(event.id); outProto.set_event_time_nanos(event.eventTime); outProto.set_down_time_nanos(event.downTime); @@ -48,11 +49,15 @@ void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent outProto.set_device_id(event.deviceId); outProto.set_display_id(event.displayId); outProto.set_classification(static_cast(event.classification)); - outProto.set_cursor_position_x(event.xCursorPosition); - outProto.set_cursor_position_y(event.yCursorPosition); outProto.set_flags(event.flags); outProto.set_policy_flags(event.policyFlags); + if (!isRedacted) { + outProto.set_cursor_position_x(event.xCursorPosition); + outProto.set_cursor_position_y(event.yCursorPosition); + outProto.set_meta_state(event.metaState); + } + for (uint32_t i = 0; i < event.pointerProperties.size(); i++) { auto* pointer = outProto.add_pointer(); @@ -66,13 +71,17 @@ void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent const auto axis = bits.clearFirstMarkedBit(); auto axisEntry = pointer->add_axis_value(); axisEntry->set_axis(axis); - axisEntry->set_value(coords.values[axisIndex]); + + if (!isRedacted) { + axisEntry->set_value(coords.values[axisIndex]); + } } } } void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event, - proto::AndroidKeyEvent& outProto) { + proto::AndroidKeyEvent& outProto, + bool isRedacted) { outProto.set_event_id(event.id); outProto.set_event_time_nanos(event.eventTime); outProto.set_down_time_nanos(event.downTime); @@ -80,21 +89,28 @@ void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& even outProto.set_action(event.action); outProto.set_device_id(event.deviceId); outProto.set_display_id(event.displayId); - outProto.set_key_code(event.keyCode); - outProto.set_scan_code(event.scanCode); - outProto.set_meta_state(event.metaState); outProto.set_repeat_count(event.repeatCount); outProto.set_flags(event.flags); outProto.set_policy_flags(event.policyFlags); + + if (!isRedacted) { + outProto.set_key_code(event.keyCode); + outProto.set_scan_code(event.scanCode); + outProto.set_meta_state(event.metaState); + } } void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent( - const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto) { + const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto, + bool isRedacted) { std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry); outProto.set_vsync_id(args.vsyncId); outProto.set_window_id(args.windowId); outProto.set_resolved_flags(args.resolvedFlags); + if (isRedacted) { + return; + } if (auto* motion = std::get_if(&args.eventEntry); motion != nullptr) { for (size_t i = 0; i < motion->pointerProperties.size(); i++) { auto* pointerProto = outProto.add_dispatched_pointer(); diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h index bab17010ff..887913f463 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h @@ -32,10 +32,12 @@ namespace android::inputdispatcher::trace { class AndroidInputEventProtoConverter { public: static void toProtoMotionEvent(const TracedMotionEvent& event, - proto::AndroidMotionEvent& outProto); - static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto); + proto::AndroidMotionEvent& outProto, bool isRedacted); + static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto, + bool isRedacted); static void toProtoWindowDispatchEvent(const WindowDispatchArgs&, - proto::AndroidWindowInputDispatchEvent& outProto); + proto::AndroidWindowInputDispatchEvent& outProto, + bool isRedacted); static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig); }; diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 998ec8a7c8..bed753c730 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -69,6 +69,30 @@ bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent( return false; } +TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel( + const TracedEventArgs& args) const { + // Check for matches with the rules in the order that they are defined. + for (const auto& rule : mConfig.rules) { + if (ruleMatches(rule, args)) { + return rule.level; + } + } + + // The event is not traced if it matched zero rules. + return TraceLevel::TRACE_LEVEL_NONE; +} + +bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, + const TracedEventArgs& args) const { + // By default, a rule will match all events. Return early if the rule does not match. + + if (rule.matchSecure.has_value() && *rule.matchSecure != args.isSecure) { + return false; + } + + return true; +} + // --- PerfettoBackend --- std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; @@ -94,54 +118,62 @@ PerfettoBackend::PerfettoBackend() { void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, const TracedEventArgs& args) { - if (args.isSecure) { - // For now, avoid tracing secure event entirely. - return; - } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; } + const TraceLevel traceLevel = dataSource->resolveTraceLevel(args); + if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) { + return; + } + const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); - auto* dispatchMotion = inputEvent->set_dispatcher_motion_event(); - AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion); + auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted() + : inputEvent->set_dispatcher_motion_event(); + AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted); }); } void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) { - if (args.isSecure) { - // For now, avoid tracing secure event entirely. - return; - } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; } + const TraceLevel traceLevel = dataSource->resolveTraceLevel(args); + if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) { + return; + } + const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); - auto* dispatchKey = inputEvent->set_dispatcher_key_event(); - AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey); + auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted() + : inputEvent->set_dispatcher_key_event(); + AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted); }); } void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs, const TracedEventArgs& args) { - if (args.isSecure) { - // For now, avoid tracing secure event entirely. - return; - } InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) { return; } + const TraceLevel traceLevel = dataSource->resolveTraceLevel(args); + if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) { + return; + } + const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); auto* inputEvent = tracePacket->set_android_input_event(); - auto* dispatchEvent = inputEvent->set_dispatcher_window_dispatch_event(); - AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent); + auto* dispatchEvent = isRedacted + ? inputEvent->set_dispatcher_window_dispatch_event_redacted() + : inputEvent->set_dispatcher_window_dispatch_event(); + AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent, + isRedacted); }); } diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index 920ea600a9..c4f80c3f0b 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -68,10 +68,13 @@ private: bool shouldIgnoreTracedInputEvent(const EventType&) const; inline ftl::Flags getFlags() const { return mConfig.flags; } + TraceLevel resolveTraceLevel(const TracedEventArgs&) const; private: const int32_t mInstanceId; TraceConfig mConfig; + + bool ruleMatches(const TraceRule&, const TracedEventArgs&) const; }; static std::once_flag sDataSourceRegistrationFlag; -- GitLab From 682820d1c9e6f9c41d83bd8fa08614216eca9c20 Mon Sep 17 00:00:00 2001 From: Rupesh Bansal Date: Wed, 13 Mar 2024 12:58:06 +0000 Subject: [PATCH 098/465] Added idlescreen timeout Bug: 310026579 Test: atest DisplayServicesTest Change-Id: I19cb005c6fc790e5e0b65833d7b7984b46c25e71 --- .../aidl/android/gui/DisplayModeSpecs.aidl | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl index af138c7539..13962fee5d 100644 --- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl +++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl @@ -41,6 +41,17 @@ parcelable DisplayModeSpecs { RefreshRateRange render; } + /** + * Refers to the time after which the idle screen's refresh rate is to be reduced + */ + parcelable IdleScreenRefreshRateConfig { + + /** + * The timeout value in milli seconds + */ + int timeoutMillis; + } + /** * Base mode ID. This is what system defaults to for all other settings, or * if the refresh rate range is not available. @@ -72,4 +83,13 @@ parcelable DisplayModeSpecs { * never smaller. */ RefreshRateRanges appRequestRanges; + + /** + * The config to represent the maximum time (in ms) for which the display can remain in an idle + * state before reducing the refresh rate to conserve power. + * Null value refers that the device is not configured to dynamically reduce the refresh rate + * based on external conditions. + * -1 refers to the current conditions requires no timeout + */ + @nullable IdleScreenRefreshRateConfig idleScreenRefreshRateConfig; } -- GitLab From 9e88d62f9045bbc87905892f44f2b7ebc2a72831 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 6 Mar 2024 17:42:39 -0500 Subject: [PATCH 099/465] SF: Match followers' refresh rate to pacesetter's Multi-display refresh rate selection was flawed: The scheduler runs the refresh rate selection algorithm for each display, and then filters the candidate modes of each follower to match the pacesetter's refresh rate. This means that: 1. The followers incorrectly consider refresh rates that don't match the pacesetter. Because the DM-specified constraint is [59, 61] Hz, some situations caused selection of a fractional rate (e.g. 59.94) instead of 60 on certain external displays. The result would be black screens for several seconds due to mode sets if the selection was not stable. 2. The followers incorrectly evaluate heuristics that should only affect the pacesetter, e.g. per-surface votes, global signals. Fix this by teaching RefreshRateSelector about follower displays. Foldables also benefit from no longer running the algorithm twice. Fixes: 324188430 Bug: 329111930 Test: No black screen for 4 seconds upon rotating mirrored YouTube. Test: 60+60 still works on foldables. Test: RefreshRateSelectorTest.pacesetterConsidered Change-Id: Ie1b27e81d860a709c85651f068fedb2b496861de --- .../Scheduler/RefreshRateSelector.cpp | 34 ++++++++-- .../Scheduler/RefreshRateSelector.h | 23 +++++-- .../surfaceflinger/Scheduler/Scheduler.cpp | 45 ++++++------- services/surfaceflinger/Scheduler/Scheduler.h | 5 ++ .../unittests/RefreshRateSelectorTest.cpp | 65 +++++++++++++------ .../tests/unittests/SchedulerTest.cpp | 26 ++++---- 6 files changed, 128 insertions(+), 70 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 56c29e2574..de4fd9088c 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -474,21 +474,23 @@ float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& lay } auto RefreshRateSelector::getRankedFrameRates(const std::vector& layers, - GlobalSignals signals) const -> RankedFrameRates { + GlobalSignals signals, Fps pacesetterFps) const + -> RankedFrameRates { + GetRankedFrameRatesCache cache{layers, signals, pacesetterFps}; + std::lock_guard lock(mLock); - if (mGetRankedFrameRatesCache && - mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) { + if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) { return mGetRankedFrameRatesCache->result; } - const auto result = getRankedFrameRatesLocked(layers, signals); - mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result}; - return result; + cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps); + mGetRankedFrameRatesCache = std::move(cache); + return mGetRankedFrameRatesCache->result; } auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector& layers, - GlobalSignals signals) const + GlobalSignals signals, Fps pacesetterFps) const -> RankedFrameRates { using namespace fps_approx_ops; ATRACE_CALL(); @@ -496,6 +498,24 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vectorgetPeakFps() == pacesetterFps; + }); + + if (!ranking.empty()) { + ATRACE_FORMAT_INSTANT("%s (Follower display)", + to_string(ranking.front().frameRateMode.fps).c_str()); + + return {ranking, kNoSignals, pacesetterFps}; + } + + ALOGW("Follower display cannot follow the pacesetter"); + } + // Keep the display at max frame rate for the duration of powering on the display. if (signals.powerOnImminent) { ALOGV("Power On Imminent"); diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 6051e8935d..a5000636ce 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -233,14 +233,18 @@ public: struct RankedFrameRates { FrameRateRanking ranking; // Ordered by descending score. GlobalSignals consideredSignals; + Fps pacesetterFps; bool operator==(const RankedFrameRates& other) const { - return ranking == other.ranking && consideredSignals == other.consideredSignals; + return ranking == other.ranking && consideredSignals == other.consideredSignals && + isApproxEqual(pacesetterFps, other.pacesetterFps); } }; - RankedFrameRates getRankedFrameRates(const std::vector&, GlobalSignals) const - EXCLUDES(mLock); + // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching + // that refresh rate. + RankedFrameRates getRankedFrameRates(const std::vector&, GlobalSignals, + Fps pacesetterFps = {}) const EXCLUDES(mLock); FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) { std::lock_guard lock(mLock); @@ -415,7 +419,8 @@ private: const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock); RankedFrameRates getRankedFrameRatesLocked(const std::vector& layers, - GlobalSignals signals) const REQUIRES(mLock); + GlobalSignals signals, Fps pacesetterFps) const + REQUIRES(mLock); // Returns number of display frames and remainder when dividing the layer refresh period by // display refresh period. @@ -534,8 +539,16 @@ private: Config::FrameRateOverride mFrameRateOverrideConfig; struct GetRankedFrameRatesCache { - std::pair, GlobalSignals> arguments; + std::vector layers; + GlobalSignals signals; + Fps pacesetterFps; + RankedFrameRates result; + + bool matches(const GetRankedFrameRatesCache& other) const { + return layers == other.layers && signals == other.signals && + isApproxEqual(pacesetterFps, other.pacesetterFps); + } }; mutable std::optional mGetRankedFrameRatesCache GUARDED_BY(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index d92edb81e0..610af2f35f 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -1146,38 +1146,31 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { ATRACE_CALL(); - using RankedRefreshRates = RefreshRateSelector::RankedFrameRates; - ui::PhysicalDisplayVector perDisplayRanking; + DisplayModeChoiceMap modeChoices; const auto globalSignals = makeGlobalSignals(); - Fps pacesetterFps; + + const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) { + auto rankedFrameRates = + pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements, + globalSignals); + + const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps; + + modeChoices.try_emplace(*mPacesetterDisplayId, + DisplayModeChoice::from(std::move(rankedFrameRates))); + return pacesetterFps; + }(); for (const auto& [id, display] : mDisplays) { + if (id == *mPacesetterDisplayId) continue; + auto rankedFrameRates = - display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, - globalSignals); - if (id == *mPacesetterDisplayId) { - pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps; - } - perDisplayRanking.push_back(std::move(rankedFrameRates)); - } + display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals, + pacesetterFps); - DisplayModeChoiceMap modeChoices; - using fps_approx_ops::operator==; - - for (auto& [rankings, signals] : perDisplayRanking) { - const auto chosenFrameRateMode = - ftl::find_if(rankings, - [&](const auto& ranking) { - return ranking.frameRateMode.fps == pacesetterFps; - }) - .transform([](const auto& scoredFrameRate) { - return scoredFrameRate.get().frameRateMode; - }) - .value_or(rankings.front().frameRateMode); - - modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(), - DisplayModeChoice{chosenFrameRateMode, signals}); + modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates))); } + return modeChoices; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 494a91bf21..c408a56fcf 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -402,6 +402,11 @@ private: DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals) : mode(std::move(mode)), consideredSignals(consideredSignals) {} + static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) { + return {rankedFrameRates.ranking.front().frameRateMode, + rankedFrameRates.consideredSignals}; + } + FrameRateMode mode; GlobalSignals consideredSignals; diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index a155f5da74..0ede612710 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -103,8 +103,9 @@ struct TestableRefreshRateSelector : RefreshRateSelector { auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; } auto getRankedFrameRates(const std::vector& layers, - GlobalSignals signals = {}) const { - const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals); + GlobalSignals signals = {}, Fps pacesetterFps = {}) const { + const auto result = + RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps); EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(), ScoredFrameRate::DescendingScore{})); @@ -114,8 +115,8 @@ struct TestableRefreshRateSelector : RefreshRateSelector { auto getRankedRefreshRatesAsPair(const std::vector& layers, GlobalSignals signals) const { - const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals); - return std::make_pair(ranking, consideredSignals); + const auto result = getRankedFrameRates(layers, signals); + return std::make_pair(result.ranking, result.consideredSignals); } FrameRateMode getBestFrameRateMode(const std::vector& layers = {}, @@ -1387,7 +1388,7 @@ TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { auto selector = createSelector(kModes_60_90, kModeId60); - auto [refreshRates, signals] = selector.getRankedFrameRates({}, {}); + auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {}); EXPECT_FALSE(signals.powerOnImminent); auto expectedRefreshRates = []() -> std::vector { @@ -1471,10 +1472,32 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { } } +TEST_P(RefreshRateSelectorTest, pacesetterConsidered) { + auto selector = createSelector(kModes_60_90, kModeId60); + constexpr RefreshRateSelector::GlobalSignals kNoSignals; + + std::vector layers = {{.weight = 1.f}}; + layers[0].vote = LayerVoteType::Min; + + // The pacesetterFps takes precedence over the LayerRequirement. + { + const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz); + EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr); + EXPECT_EQ(kNoSignals, result.consideredSignals); + } + + // The pacesetterFps takes precedence over GlobalSignals. + { + const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz); + EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr); + EXPECT_EQ(kNoSignals, result.consideredSignals); + } +} + TEST_P(RefreshRateSelectorTest, touchConsidered) { auto selector = createSelector(kModes_60_90, kModeId60); - auto [_, signals] = selector.getRankedFrameRates({}, {}); + auto signals = selector.getRankedFrameRates({}, {}).consideredSignals; EXPECT_FALSE(signals.touch); std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true}); @@ -2363,7 +2386,7 @@ TEST_P(RefreshRateSelectorTest, lr.name = "60Hz ExplicitDefault"; lr.focused = true; - const auto [rankedFrameRate, signals] = + const auto [rankedFrameRate, signals, _] = selector.getRankedFrameRates(layers, {.touch = true, .idle = true}); EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60); @@ -2587,7 +2610,7 @@ TEST_P(RefreshRateSelectorTest, EXPECT_EQ(SetPolicyResult::Changed, selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}})); - const auto [ranking, signals] = selector.getRankedFrameRates({}, {}); + const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {}); EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90); EXPECT_FALSE(signals.touch); @@ -2971,7 +2994,7 @@ TEST_P(RefreshRateSelectorTest, idle) { layers[0].vote = voteType; layers[0].desiredRefreshRate = 90_Hz; - const auto [ranking, signals] = + const auto [ranking, signals, _] = selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. @@ -3121,16 +3144,17 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) { auto selector = createSelector(kModes_30_60_72_90_120, kModeId60); using GlobalSignals = RefreshRateSelector::GlobalSignals; - const auto args = std::make_pair(std::vector{}, - GlobalSignals{.touch = true, .idle = true}); - const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{ {90_Hz, kMode90}}}, GlobalSignals{.touch = true}}; - selector.mutableGetRankedRefreshRatesCache() = {args, result}; + selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector{}, + .signals = GlobalSignals{.touch = true, + .idle = true}, + .result = result}; - EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second)); + const auto& cache = *selector.mutableGetRankedRefreshRatesCache(); + EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals)); } TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) { @@ -3138,15 +3162,18 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) { EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache()); - std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; - RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true}; + const std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; + const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true}; + const Fps pacesetterFps = 60_Hz; - const auto result = selector.getRankedFrameRates(layers, globalSignals); + const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps); const auto& cache = selector.mutableGetRankedRefreshRatesCache(); ASSERT_TRUE(cache); - EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals)); + EXPECT_EQ(cache->layers, layers); + EXPECT_EQ(cache->signals, globalSignals); + EXPECT_EQ(cache->pacesetterFps, pacesetterFps); EXPECT_EQ(cache->result, result); } @@ -4073,7 +4100,7 @@ TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) { layers[0].vote = voteType; layers[0].desiredRefreshRate = 90_Hz; - const auto [ranking, signals] = + const auto [ranking, signals, _] = selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index d4735c7558..8ac8edabcc 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -362,7 +362,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, FrameRateMode{60_Hz, kDisplay2Mode60}, - globalSignals); + GlobalSignals{}); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; @@ -381,7 +381,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, - globalSignals); + GlobalSignals{}); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); @@ -400,7 +400,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, - globalSignals); + GlobalSignals{}); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); @@ -422,10 +422,10 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120}, globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, - globalSignals)(kDisplayId3, - FrameRateMode{60_Hz, - kDisplay3Mode60}, - globalSignals); + GlobalSignals{})(kDisplayId3, + FrameRateMode{60_Hz, + kDisplay3Mode60}, + GlobalSignals{}); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); @@ -440,12 +440,12 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { expectedChoices = ftl::init::map< const PhysicalDisplayId&, DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60}, - globalSignals)(kDisplayId2, - FrameRateMode{60_Hz, kDisplay2Mode60}, - globalSignals)(kDisplayId3, - FrameRateMode{60_Hz, - kDisplay3Mode60}, - globalSignals); + GlobalSignals{})(kDisplayId2, + FrameRateMode{60_Hz, kDisplay2Mode60}, + GlobalSignals{})(kDisplayId3, + FrameRateMode{60_Hz, + kDisplay3Mode60}, + globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); -- GitLab From 2b57f461e21a872497b284daf64debf75b091b9f Mon Sep 17 00:00:00 2001 From: Ying Wei Date: Sat, 16 Mar 2024 05:24:39 +0000 Subject: [PATCH 100/465] Correct SF frame interval. When SF fps doesn't match vsync rate (either due to vrr or frame rate override), the frame interval calculation in Scheduler should take this into account. Bug: 328352850 Test: atest CtsSurfaceControlTests Test: atest libsurfaceflinger_unittest Test: atest FrameRateOverrideTest Change-Id: I3261c7f2245fcb64c3e79bb3dcb21f1b0e44a395 (cherry picked from commit af854add2313fc931a7bf6be9eb2f5cfbc3e448e) Merged-In: I3261c7f2245fcb64c3e79bb3dcb21f1b0e44a395 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 7 +++++-- services/surfaceflinger/Scheduler/Scheduler.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 3f9168252b..3a05b0bf88 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -306,8 +306,11 @@ Period Scheduler::getVsyncPeriod(uid_t uid) { const auto pacesetterOpt = pacesetterDisplayLocked(); LOG_ALWAYS_FATAL_IF(!pacesetterOpt); const Display& pacesetter = *pacesetterOpt; - return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps, - pacesetter.schedulePtr->period()); + const FrameRateMode& frameRateMode = pacesetter.selectorPtr->getActiveMode(); + const auto refreshRate = frameRateMode.fps; + const auto displayVsync = frameRateMode.modePtr->getVsyncRate(); + const auto numPeriod = RefreshRateSelector::getFrameRateDivisor(displayVsync, refreshRate); + return std::make_pair(refreshRate, numPeriod * pacesetter.schedulePtr->period()); }(); const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod(); diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 09f75fdaca..4fc2d539d6 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -437,6 +437,7 @@ private: // IEventThreadCallback overrides bool throttleVsync(TimePoint, uid_t) override; + // Get frame interval Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock); void resync() override EXCLUDES(mDisplayLock); void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock); -- GitLab From 68a0d27ac0de654ecf04577665a1cbb19ecb5cc1 Mon Sep 17 00:00:00 2001 From: Zimuzo Ezeozue Date: Tue, 19 Mar 2024 09:52:41 +0000 Subject: [PATCH 101/465] Revert^2 "Fix check for whether a trace tag is enabled" 83483148c38b18110a4beec5dd554e9beed74ccb Change-Id: I9e37dbb1e6396a8456c57df68932c96dc289d804 Bug: 330172681 --- libs/tracing_perfetto/include/tracing_perfetto.h | 2 +- libs/tracing_perfetto/tracing_perfetto.cpp | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h index 4e3c83fca3..2c1c2a49e7 100644 --- a/libs/tracing_perfetto/include/tracing_perfetto.h +++ b/libs/tracing_perfetto/include/tracing_perfetto.h @@ -46,7 +46,7 @@ Result traceInstantForTrack(uint64_t category, const char* trackName, Result traceCounter(uint64_t category, const char* name, int64_t value); -uint64_t getEnabledCategories(); +bool isTagEnabled(uint64_t category); } // namespace tracing_perfetto diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp index 19d1eb639e..6f716eea9a 100644 --- a/libs/tracing_perfetto/tracing_perfetto.cpp +++ b/libs/tracing_perfetto/tracing_perfetto.cpp @@ -130,12 +130,13 @@ Result traceCounter(uint64_t category, const char* name, int64_t value) { } } -uint64_t getEnabledCategories() { - if (internal::isPerfettoRegistered()) { - // TODO(b/303199244): Return only enabled categories and not all registered ones - return internal::getDefaultCategories(); +bool isTagEnabled(uint64_t category) { + struct PerfettoTeCategory* perfettoTeCategory = + internal::toPerfettoCategory(category); + if (perfettoTeCategory != nullptr) { + return true; } else { - return atrace_get_enabled_tags(); + return (atrace_get_enabled_tags() & category) != 0; } } -- GitLab From ef710a3ab7306d2db144ed678aa98e4c712ae52f Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Sat, 9 Mar 2024 00:45:39 +0000 Subject: [PATCH 102/465] Add APersistableBundle to lldnk Test: lunch aosp_cf_x86_64_only_phone-next-userdebug Test: VtsHalWifiSupplicantStaNetworkTargetTest Bug: 328328863 (cherry picked from https://android-review.googlesource.com/q/commit:eb653ee3aeaebc02c3439655673b135d397ecdec) Merged-In: I4bc27098f09213564e4ec39036783b1a853a11b3 Change-Id: I4bc27098f09213564e4ec39036783b1a853a11b3 --- libs/binder/ndk/Android.bp | 3 + .../android/persistable_bundle_aidl.h | 95 ++++++----- .../include_ndk/android/persistable_bundle.h | 157 ++++++++++-------- 3 files changed, 142 insertions(+), 113 deletions(-) diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp index ccf3ce891f..30dbdddc60 100644 --- a/libs/binder/ndk/Android.bp +++ b/libs/binder/ndk/Android.bp @@ -40,6 +40,7 @@ cc_library { llndk: { symbol_file: "libbinder_ndk.map.txt", + export_llndk_headers: ["libvendorsupport_llndk_headers"], }, export_include_dirs: [ @@ -79,9 +80,11 @@ cc_library { ], header_libs: [ + "libvendorsupport_llndk_headers", "jni_headers", ], export_header_lib_headers: [ + "libvendorsupport_llndk_headers", "jni_headers", ], diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h index 864ff50831..3aacbe9d7a 100644 --- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h +++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h @@ -24,6 +24,13 @@ namespace aidl::android::os { +#if defined(__ANDROID_VENDOR__) +#define AT_LEAST_V_OR_202404 constexpr(__ANDROID_VENDOR_API__ >= 202404) +#else +// TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized +#define AT_LEAST_V_OR_202404 (__builtin_available(android __ANDROID_API_FUTURE__, *)) +#endif + /** * Wrapper class that enables interop with AIDL NDK generation * Takes ownership of the APersistableBundle* given to it in reset() and will automatically @@ -32,7 +39,7 @@ namespace aidl::android::os { class PersistableBundle { public: PersistableBundle() noexcept { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { mPBundle = APersistableBundle_new(); } } @@ -42,13 +49,13 @@ class PersistableBundle { PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {} // duplicates, does not take ownership of the APersistableBundle* PersistableBundle(const PersistableBundle& other) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { mPBundle = APersistableBundle_dup(other.mPBundle); } } // duplicates, does not take ownership of the APersistableBundle* PersistableBundle& operator=(const PersistableBundle& other) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { mPBundle = APersistableBundle_dup(other.mPBundle); } return *this; @@ -58,7 +65,7 @@ class PersistableBundle { binder_status_t readFromParcel(const AParcel* _Nonnull parcel) { reset(); - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_readFromParcel(parcel, &mPBundle); } else { return STATUS_INVALID_OPERATION; @@ -69,7 +76,7 @@ class PersistableBundle { if (!mPBundle) { return STATUS_BAD_VALUE; } - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_writeToParcel(mPBundle, parcel); } else { return STATUS_INVALID_OPERATION; @@ -84,7 +91,7 @@ class PersistableBundle { */ void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept { if (mPBundle) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { APersistableBundle_delete(mPBundle); } mPBundle = nullptr; @@ -97,7 +104,7 @@ class PersistableBundle { * what should be used to check for equality. */ bool deepEquals(const PersistableBundle& rhs) const { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_isEqual(get(), rhs.get()); } else { return false; @@ -136,7 +143,7 @@ class PersistableBundle { inline std::string toString() const { if (!mPBundle) { return ""; - } else if (__builtin_available(android __ANDROID_API_V__, *)) { + } else if AT_LEAST_V_OR_202404 { std::ostringstream os; os << "& vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { // std::vector has no ::data(). int32_t num = vec.size(); if (num > 0) { @@ -210,7 +217,7 @@ class PersistableBundle { } void putIntVector(const std::string& key, const std::vector& vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { int32_t num = vec.size(); if (num > 0) { APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num); @@ -218,7 +225,7 @@ class PersistableBundle { } } void putLongVector(const std::string& key, const std::vector& vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { int32_t num = vec.size(); if (num > 0) { APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num); @@ -226,7 +233,7 @@ class PersistableBundle { } } void putDoubleVector(const std::string& key, const std::vector& vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { int32_t num = vec.size(); if (num > 0) { APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num); @@ -234,7 +241,7 @@ class PersistableBundle { } } void putStringVector(const std::string& key, const std::vector& vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { int32_t num = vec.size(); if (num > 0) { char** inVec = (char**)malloc(num * sizeof(char*)); @@ -249,13 +256,13 @@ class PersistableBundle { } } void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle); } } bool getBoolean(const std::string& key, bool* _Nonnull val) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_getBoolean(mPBundle, key.c_str(), val); } else { return false; @@ -263,7 +270,7 @@ class PersistableBundle { } bool getInt(const std::string& key, int32_t* _Nonnull val) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_getInt(mPBundle, key.c_str(), val); } else { return false; @@ -271,7 +278,7 @@ class PersistableBundle { } bool getLong(const std::string& key, int64_t* _Nonnull val) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_getLong(mPBundle, key.c_str(), val); } else { return false; @@ -279,7 +286,7 @@ class PersistableBundle { } bool getDouble(const std::string& key, double* _Nonnull val) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return APersistableBundle_getDouble(mPBundle, key.c_str(), val); } else { return false; @@ -291,7 +298,7 @@ class PersistableBundle { } bool getString(const std::string& key, std::string* _Nonnull val) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { char* outString = nullptr; bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString, &stringAllocator, nullptr); @@ -309,7 +316,7 @@ class PersistableBundle { const char* _Nonnull, T* _Nullable, int32_t), const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, std::vector* _Nonnull vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { int32_t bytes = 0; // call first with nullptr to get required size in bytes bytes = getVec(pBundle, key, nullptr, 0); @@ -331,28 +338,28 @@ class PersistableBundle { } bool getBooleanVector(const std::string& key, std::vector* _Nonnull vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getVecInternal(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(), vec); } return false; } bool getIntVector(const std::string& key, std::vector* _Nonnull vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getVecInternal(&APersistableBundle_getIntVector, mPBundle, key.c_str(), vec); } return false; } bool getLongVector(const std::string& key, std::vector* _Nonnull vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getVecInternal(&APersistableBundle_getLongVector, mPBundle, key.c_str(), vec); } return false; } bool getDoubleVector(const std::string& key, std::vector* _Nonnull vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getVecInternal(&APersistableBundle_getDoubleVector, mPBundle, key.c_str(), vec); } @@ -377,7 +384,7 @@ class PersistableBundle { } bool getStringVector(const std::string& key, std::vector* _Nonnull vec) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0, &stringAllocator, nullptr); if (bytes > 0) { @@ -394,7 +401,7 @@ class PersistableBundle { } bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { APersistableBundle* bundle = nullptr; bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle); if (ret) { @@ -426,77 +433,77 @@ class PersistableBundle { } std::set getBooleanKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getBooleanKeys, mPBundle); } else { return {}; } } std::set getIntKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getIntKeys, mPBundle); } else { return {}; } } std::set getLongKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getLongKeys, mPBundle); } else { return {}; } } std::set getDoubleKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getDoubleKeys, mPBundle); } else { return {}; } } std::set getStringKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getStringKeys, mPBundle); } else { return {}; } } std::set getBooleanVectorKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle); } else { return {}; } } std::set getIntVectorKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle); } else { return {}; } } std::set getLongVectorKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle); } else { return {}; } } std::set getDoubleVectorKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle); } else { return {}; } } std::set getStringVectorKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle); } else { return {}; } } std::set getPersistableBundleKeys() { - if (__builtin_available(android __ANDROID_API_V__, *)) { + if AT_LEAST_V_OR_202404 { return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle); } else { return {}; diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h index 98c0cb2514..1247e8e075 100644 --- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h +++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h @@ -17,6 +17,11 @@ #pragma once #include +#if defined(__ANDROID_VENDOR__) +#include +#else +#define __INTRODUCED_IN_LLNDK(x) +#endif #include #include #include @@ -67,25 +72,26 @@ typedef char* _Nullable (*_Nonnull APersistableBundle_stringAllocator)(int32_t s /** * Create a new APersistableBundle. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \return Pointer to a new APersistableBundle */ -APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__); +APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Create a new APersistableBundle based off an existing APersistableBundle. * This is a deep copy, so the new APersistableBundle has its own values from * copying the original underlying PersistableBundle. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to duplicate * * \return Pointer to a new APersistableBundle */ APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Delete an APersistableBundle. This must always be called when finished using @@ -93,15 +99,15 @@ APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _ * * \param pBundle to delete. No-op if null. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_delete(APersistableBundle* _Nullable pBundle) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Check for equality of APersistableBundles. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param lhs bundle to compare against the other param * \param rhs bundle to compare against the other param @@ -110,12 +116,12 @@ void APersistableBundle_delete(APersistableBundle* _Nullable pBundle) */ bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs, const APersistableBundle* _Nonnull rhs) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Read an APersistableBundle from an AParcel. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param parcel to read from * \param outPBundle bundle to write to @@ -129,12 +135,12 @@ bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs, */ binder_status_t APersistableBundle_readFromParcel( const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Write an APersistableBundle to an AParcel. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle bundle to write to the parcel * \param parcel to write to @@ -149,25 +155,25 @@ binder_status_t APersistableBundle_readFromParcel( */ binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle, AParcel* _Nonnull parcel) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get the size of an APersistableBundle. This is the number of mappings in the * object. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to get the size of (number of mappings) * * \return number of mappings in the object */ int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Erase any entries added with the provided key. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 to erase @@ -175,7 +181,7 @@ int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle) * \return number of entries erased. Either 0 or 1. */ int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Put a boolean associated with the provided key. @@ -185,10 +191,11 @@ int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const cha * \param key for the mapping in UTF-8 * \param value to put for the mapping * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, - bool val) __INTRODUCED_IN(__ANDROID_API_V__); + bool val) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put an int32_t associated with the provided key. @@ -198,10 +205,11 @@ void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const c * \param key for the mapping in UTF-8 * \param val value to put for the mapping * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, - int32_t val) __INTRODUCED_IN(__ANDROID_API_V__); + int32_t val) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put an int64_t associated with the provided key. @@ -211,10 +219,11 @@ void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* * \param key for the mapping in UTF-8 * \param val value to put for the mapping * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, - int64_t val) __INTRODUCED_IN(__ANDROID_API_V__); + int64_t val) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put a double associated with the provided key. @@ -224,10 +233,11 @@ void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char * \param key for the mapping in UTF-8 * \param val value to put for the mapping * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, - double val) __INTRODUCED_IN(__ANDROID_API_V__); + double val) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put a string associated with the provided key. @@ -238,10 +248,11 @@ void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const ch * \param key for the mapping in UTF-8 * \param vec vector to put for the mapping * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, - const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__); + const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put a boolean vector associated with the provided key. @@ -253,11 +264,12 @@ void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const ch * \param vec vector to put for the mapping * \param num number of elements in the vector * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, const bool* _Nonnull vec, - int32_t num) __INTRODUCED_IN(__ANDROID_API_V__); + int32_t num) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put an int32_t vector associated with the provided key. @@ -269,11 +281,11 @@ void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle, * \param vec vector to put for the mapping * \param num number of elements in the vector * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, const int32_t* _Nonnull vec, int32_t num) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Put an int64_t vector associated with the provided key. @@ -285,11 +297,12 @@ void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const * \param vec vector to put for the mapping * \param num number of elements in the vector * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, const int64_t* _Nonnull vec, - int32_t num) __INTRODUCED_IN(__ANDROID_API_V__); + int32_t num) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put a double vector associated with the provided key. @@ -301,11 +314,12 @@ void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle, * \param vec vector to put for the mapping * \param num number of elements in the vector * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, const double* _Nonnull vec, - int32_t num) __INTRODUCED_IN(__ANDROID_API_V__); + int32_t num) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Put a string vector associated with the provided key. @@ -317,12 +331,12 @@ void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle, * \param vec vector to put for the mapping * \param num number of elements in the vector * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, const char* _Nullable const* _Nullable vec, int32_t num) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Put an APersistableBundle associated with the provided key. @@ -333,17 +347,17 @@ void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle, * \param key for the mapping in UTF-8 * \param val value to put for the mapping * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. */ void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, const APersistableBundle* _Nonnull val) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get a boolean associated with the provided key. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 @@ -353,12 +367,12 @@ void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundl */ bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, bool* _Nonnull val) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get an int32_t associated with the provided key. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 @@ -367,12 +381,13 @@ bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle, * \return true if a value exists for the provided key */ bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, - int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__); + int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get an int64_t associated with the provided key. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 @@ -382,12 +397,12 @@ bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const */ bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, int64_t* _Nonnull val) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get a double associated with the provided key. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 @@ -397,13 +412,13 @@ bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle, */ bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, double* _Nonnull val) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get a string associated with the provided key. * The caller is responsible for freeing the returned data. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 @@ -418,7 +433,8 @@ bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle, int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, char* _Nullable* _Nonnull val, APersistableBundle_stringAllocator stringAllocator, - void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__); + void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get a boolean vector associated with the provided key and place it in the @@ -445,7 +461,7 @@ int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle, int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, bool* _Nullable buffer, int32_t bufferSizeBytes) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get an int32_t vector associated with the provided key and place it in the @@ -471,7 +487,8 @@ int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull p */ int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, int32_t* _Nullable buffer, - int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__); + int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get an int64_t vector associated with the provided key and place it in the @@ -497,8 +514,8 @@ int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBund */ int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, int64_t* _Nullable buffer, - int32_t bufferSizeBytes) - __INTRODUCED_IN(__ANDROID_API_V__); + int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get a double vector associated with the provided key and place it in the @@ -525,7 +542,7 @@ int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBun int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, double* _Nullable buffer, int32_t bufferSizeBytes) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get a string vector associated with the provided key and place it in the @@ -562,12 +579,12 @@ int32_t APersistableBundle_getStringVector(const APersistableBundle* _Nonnull pB int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get an APersistableBundle* associated with the provided key. * - * Available since API level __ANDROID_API_V__. + * Available since API level 202404. * * \param pBundle to operate on * \param key for the mapping in UTF-8 @@ -581,7 +598,7 @@ int32_t APersistableBundle_getStringVector(const APersistableBundle* _Nonnull pB bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key, APersistableBundle* _Nullable* _Nonnull outBundle) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -614,7 +631,7 @@ int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* _Nonnull pBu int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -645,7 +662,8 @@ int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* _Nonnull pBu int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, - void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__); + void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -676,7 +694,8 @@ int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, - void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__); + void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -708,8 +727,8 @@ int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* _Nonnull pBun char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, - void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -741,8 +760,8 @@ int32_t APersistableBundle_getStringKeys(const APersistableBundle* _Nonnull pBun char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, - void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) + __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -775,7 +794,7 @@ int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* _Nonnu int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -808,7 +827,7 @@ int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* _Nonnull p int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -841,7 +860,7 @@ int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* _Nonnull int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -873,7 +892,7 @@ int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* _Nonnul int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -906,7 +925,7 @@ int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* _Nonnul int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, void* _Nullable context) - __INTRODUCED_IN(__ANDROID_API_V__); + __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); /** * Get all of the keys associated with this specific type and place it in the @@ -937,6 +956,6 @@ int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* _Nonnul int32_t APersistableBundle_getPersistableBundleKeys( const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator, - void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__); + void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404); __END_DECLS -- GitLab From ca05028642a0190e2343a8b9597e90f396ae04c5 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Fri, 15 Mar 2024 13:27:06 -0400 Subject: [PATCH 103/465] Create and plumb SkiaBackendTexture abstraction layer over GrBackendTexture This means AutoBackendTexture is not aware of backend-specific texture type and API details, and will be able to accept either a Ganesh or Graphite variant of SkiaBackendTexture. Also delegated SkiaBackendTexture creation to SkiaGpuContext, so that backend-specific contexts handle creating backend-specifc textures. Test: manual validation (GL+VK) & existing tests (refactor) Bug: b/293371537 Change-Id: Ia65306cc825b71fe0b89c7f8545ce1c71a81d86b --- libs/renderengine/Android.bp | 1 + libs/renderengine/skia/AutoBackendTexture.cpp | 149 +++------------- libs/renderengine/skia/AutoBackendTexture.h | 29 ++-- libs/renderengine/skia/SkiaRenderEngine.cpp | 16 +- .../skia/compat/GaneshBackendTexture.cpp | 161 ++++++++++++++++++ .../skia/compat/GaneshBackendTexture.h | 57 +++++++ .../skia/compat/GaneshGpuContext.cpp | 10 ++ .../skia/compat/GaneshGpuContext.h | 3 + .../skia/compat/SkiaBackendTexture.h | 85 +++++++++ .../renderengine/skia/compat/SkiaGpuContext.h | 7 + 10 files changed, 363 insertions(+), 155 deletions(-) create mode 100644 libs/renderengine/skia/compat/GaneshBackendTexture.cpp create mode 100644 libs/renderengine/skia/compat/GaneshBackendTexture.h create mode 100644 libs/renderengine/skia/compat/SkiaBackendTexture.h diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 39902b1635..5cd0ce530f 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -88,6 +88,7 @@ filegroup { "skia/SkiaGLRenderEngine.cpp", "skia/SkiaVkRenderEngine.cpp", "skia/VulkanInterface.cpp", + "skia/compat/GaneshBackendTexture.cpp", "skia/compat/GaneshGpuContext.cpp", "skia/debug/CaptureTimer.cpp", "skia/debug/CommonPool.cpp", diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp index 02e7337053..8aeef9f4bc 100644 --- a/libs/renderengine/skia/AutoBackendTexture.cpp +++ b/libs/renderengine/skia/AutoBackendTexture.cpp @@ -20,71 +20,21 @@ #define LOG_TAG "RenderEngine" #define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include -#include -#include -#include -#include -#include -#include -#include "ColorSpaces.h" -#include "log/log_main.h" -#include "utils/Trace.h" +#include +#include + +#include "compat/SkiaBackendTexture.h" + +#include +#include namespace android { namespace renderengine { namespace skia { -AutoBackendTexture::AutoBackendTexture(SkiaGpuContext* context, AHardwareBuffer* buffer, - bool isOutputBuffer, CleanupManager& cleanupMgr) - : mGrContext(context->grDirectContext()), - mCleanupMgr(cleanupMgr), - mIsOutputBuffer(isOutputBuffer) { - ATRACE_CALL(); - - AHardwareBuffer_Desc desc; - AHardwareBuffer_describe(buffer, &desc); - bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); - GrBackendFormat backendFormat; - - GrBackendApi backend = mGrContext->backend(); - if (backend == GrBackendApi::kOpenGL) { - backendFormat = - GrAHardwareBufferUtils::GetGLBackendFormat(mGrContext.get(), desc.format, false); - mBackendTexture = - GrAHardwareBufferUtils::MakeGLBackendTexture(mGrContext.get(), buffer, desc.width, - desc.height, &mDeleteProc, - &mUpdateProc, &mImageCtx, - createProtectedImage, backendFormat, - isOutputBuffer); - } else if (backend == GrBackendApi::kVulkan) { - backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(mGrContext.get(), buffer, - desc.format, false); - mBackendTexture = - GrAHardwareBufferUtils::MakeVulkanBackendTexture(mGrContext.get(), buffer, - desc.width, desc.height, - &mDeleteProc, &mUpdateProc, - &mImageCtx, createProtectedImage, - backendFormat, isOutputBuffer); - } else { - LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast(backend)); - } - - mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); - if (!mBackendTexture.isValid() || !desc.width || !desc.height) { - LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d " - "isWriteable:%d format:%d", - this, desc.width, desc.height, createProtectedImage, isOutputBuffer, - desc.format); - } -} - -AutoBackendTexture::~AutoBackendTexture() { - if (mBackendTexture.isValid()) { - mDeleteProc(mImageCtx); - mBackendTexture = {}; - } -} +AutoBackendTexture::AutoBackendTexture(std::unique_ptr backendTexture, + CleanupManager& cleanupMgr) + : mCleanupMgr(cleanupMgr), mBackendTexture(std::move(backendTexture)) {} void AutoBackendTexture::unref(bool releaseLocalResources) { if (releaseLocalResources) { @@ -112,93 +62,32 @@ void AutoBackendTexture::releaseImageProc(SkImages::ReleaseContext releaseContex textureRelease->unref(false); } -void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace, - SkColorType colorType) { - switch (tex.backend()) { - case GrBackendApi::kOpenGL: { - GrGLTextureInfo textureInfo; - bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &textureInfo); - LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d" - "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i " - "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u" - " colorType %i", - msg, tex.isValid(), static_cast(dataspace), tex.width(), - tex.height(), tex.hasMipmaps(), tex.isProtected(), - static_cast(tex.textureType()), retrievedTextureInfo, - textureInfo.fTarget, textureInfo.fFormat, colorType); - break; - } - case GrBackendApi::kVulkan: { - GrVkImageInfo imageInfo; - bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo); - LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d" - "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i " - "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i " - "fSampleCount: %u fLevelCount: %u colorType %i", - msg, tex.isValid(), static_cast(dataspace), tex.width(), - tex.height(), tex.hasMipmaps(), tex.isProtected(), - static_cast(tex.textureType()), retrievedImageInfo, - imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount, - colorType); - break; - } - default: - LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg, static_cast(tex.backend())); - break; - } -} - sk_sp AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) { ATRACE_CALL(); - if (mBackendTexture.isValid()) { - mUpdateProc(mImageCtx, mGrContext.get()); - } - - auto colorType = mColorType; - if (alphaType == kOpaque_SkAlphaType) { - if (colorType == kRGBA_8888_SkColorType) { - colorType = kRGB_888x_SkColorType; - } - } - - sk_sp image = - SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin, - colorType, alphaType, toSkColorSpace(dataspace), - releaseImageProc, this); - if (image.get()) { - // The following ref will be counteracted by releaseProc, when SkImage is discarded. - ref(); - } + sk_sp image = mBackendTexture->makeImage(alphaType, dataspace, releaseImageProc, this); + // The following ref will be counteracted by releaseProc, when SkImage is discarded. + ref(); mImage = image; mDataspace = dataspace; - if (!mImage) { - logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType); - } return mImage; } sk_sp AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) { ATRACE_CALL(); - LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture"); + LOG_ALWAYS_FATAL_IF(!mBackendTexture->isOutputBuffer(), + "You can't generate an SkSurface for a read-only texture"); if (!mSurface.get() || mDataspace != dataspace) { sk_sp surface = - SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture, - kTopLeft_GrSurfaceOrigin, 0, mColorType, - toSkColorSpace(dataspace), nullptr, - releaseSurfaceProc, this); - if (surface.get()) { - // The following ref will be counteracted by releaseProc, when SkSurface is discarded. - ref(); - } + mBackendTexture->makeSurface(dataspace, releaseSurfaceProc, this); + // The following ref will be counteracted by releaseProc, when SkSurface is discarded. + ref(); + mSurface = surface; } mDataspace = dataspace; - if (!mSurface) { - logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType); - } return mSurface; } diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h index 1d5b5655db..74daf471fa 100644 --- a/libs/renderengine/skia/AutoBackendTexture.h +++ b/libs/renderengine/skia/AutoBackendTexture.h @@ -16,7 +16,6 @@ #pragma once -#include #include #include #include @@ -24,9 +23,9 @@ #include #include "android-base/macros.h" -#include "compat/SkiaGpuContext.h" +#include "compat/SkiaBackendTexture.h" -#include +#include #include namespace android { @@ -81,9 +80,8 @@ public: // of shared ownership with Skia objects, so we wrap it here instead. class LocalRef { public: - LocalRef(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, - CleanupManager& cleanupMgr) { - mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr); + LocalRef(std::unique_ptr backendTexture, CleanupManager& cleanupMgr) { + mTexture = new AutoBackendTexture(std::move(backendTexture), cleanupMgr); mTexture->ref(); } @@ -105,7 +103,7 @@ public: return mTexture->getOrCreateSurface(dataspace); } - SkColorType colorType() const { return mTexture->mColorType; } + SkColorType colorType() const { return mTexture->mBackendTexture->internalColorType(); } DISALLOW_COPY_AND_ASSIGN(LocalRef); @@ -116,12 +114,13 @@ public: private: DISALLOW_COPY_AND_ASSIGN(AutoBackendTexture); - // Creates a GrBackendTexture whose contents come from the provided buffer. - AutoBackendTexture(SkiaGpuContext* context, AHardwareBuffer* buffer, bool isOutputBuffer, + // Creates an AutoBackendTexture to manage the lifecycle of a given SkiaBackendTexture, which is + // in turn backed by an underlying backend-specific texture type. + AutoBackendTexture(std::unique_ptr backendTexture, CleanupManager& cleanupMgr); // The only way to invoke dtor is with unref, when mUsageCount is 0. - ~AutoBackendTexture(); + ~AutoBackendTexture() = default; void ref() { mUsageCount++; } @@ -137,24 +136,16 @@ private: // Makes a new SkSurface from the texture content, if needed. sk_sp getOrCreateSurface(ui::Dataspace dataspace); - GrBackendTexture mBackendTexture; - GrAHardwareBufferUtils::DeleteImageProc mDeleteProc; - GrAHardwareBufferUtils::UpdateImageProc mUpdateProc; - GrAHardwareBufferUtils::TexImageCtx mImageCtx; - - // TODO: b/293371537 - Graphite abstractions for ABT. - const sk_sp mGrContext = nullptr; CleanupManager& mCleanupMgr; static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext); static void releaseImageProc(SkImages::ReleaseContext releaseContext); + std::unique_ptr mBackendTexture; int mUsageCount = 0; - const bool mIsOutputBuffer; sk_sp mImage = nullptr; sk_sp mSurface = nullptr; ui::Dataspace mDataspace = ui::Dataspace::UNKNOWN; - SkColorType mColorType = kUnknown_SkColorType; }; } // namespace skia diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 27daeba092..9e8fe6802d 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -80,6 +80,7 @@ #include "filters/KawaseBlurFilter.h" #include "filters/LinearEffect.h" #include "log/log_main.h" +#include "skia/compat/SkiaBackendTexture.h" #include "skia/debug/SkiaCapture.h" #include "skia/debug/SkiaMemoryReporter.h" #include "skia/filters/StretchShaderFactory.h" @@ -417,9 +418,11 @@ void SkiaRenderEngine::mapExternalTextureBuffer(const sp& buffer, if (FlagManager::getInstance().renderable_buffer_usage()) { isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER; } - std::shared_ptr imageTextureRef = - std::make_shared(context, buffer->toAHardwareBuffer(), - isRenderable, mTextureCleanupMgr); + std::unique_ptr backendTexture = + context->makeBackendTexture(buffer->toAHardwareBuffer(), isRenderable); + auto imageTextureRef = + std::make_shared(std::move(backendTexture), + mTextureCleanupMgr); cache.insert({buffer->getId(), imageTextureRef}); } } @@ -470,9 +473,10 @@ std::shared_ptr SkiaRenderEngine::getOrCreateBacke return it->second; } } - return std::make_shared(getActiveContext(), - buffer->toAHardwareBuffer(), - isOutputBuffer, mTextureCleanupMgr); + std::unique_ptr backendTexture = + getActiveContext()->makeBackendTexture(buffer->toAHardwareBuffer(), isOutputBuffer); + return std::make_shared(std::move(backendTexture), + mTextureCleanupMgr); } bool SkiaRenderEngine::canSkipPostRenderCleanup() const { diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.cpp b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp new file mode 100644 index 0000000000..d246466965 --- /dev/null +++ b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp @@ -0,0 +1,161 @@ +/* + * Copyright 2024 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. + */ + +#include "GaneshBackendTexture.h" + +#undef LOG_TAG +#define LOG_TAG "RenderEngine" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include +#include +#include +#include +#include +#include +#include + +#include "skia/ColorSpaces.h" +#include "skia/compat/SkiaBackendTexture.h" + +#include +#include +#include + +namespace android::renderengine::skia { + +GaneshBackendTexture::GaneshBackendTexture(sk_sp grContext, + AHardwareBuffer* buffer, bool isOutputBuffer) + : SkiaBackendTexture(buffer, isOutputBuffer), mGrContext(grContext) { + ATRACE_CALL(); + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(buffer, &desc); + const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); + + GrBackendFormat backendFormat; + const GrBackendApi graphicsApi = grContext->backend(); + if (graphicsApi == GrBackendApi::kOpenGL) { + backendFormat = + GrAHardwareBufferUtils::GetGLBackendFormat(grContext.get(), desc.format, false); + mBackendTexture = + GrAHardwareBufferUtils::MakeGLBackendTexture(grContext.get(), buffer, desc.width, + desc.height, &mDeleteProc, + &mUpdateProc, &mImageCtx, + createProtectedImage, backendFormat, + isOutputBuffer); + } else if (graphicsApi == GrBackendApi::kVulkan) { + backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(grContext.get(), buffer, + desc.format, false); + mBackendTexture = + GrAHardwareBufferUtils::MakeVulkanBackendTexture(grContext.get(), buffer, + desc.width, desc.height, + &mDeleteProc, &mUpdateProc, + &mImageCtx, createProtectedImage, + backendFormat, isOutputBuffer); + } else { + LOG_ALWAYS_FATAL("Unexpected graphics API %u", static_cast(graphicsApi)); + } + + if (!mBackendTexture.isValid() || !desc.width || !desc.height) { + LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d " + "isWriteable:%d format:%d", + this, desc.width, desc.height, createProtectedImage, isOutputBuffer, + desc.format); + } +} + +GaneshBackendTexture::~GaneshBackendTexture() { + if (mBackendTexture.isValid()) { + mDeleteProc(mImageCtx); + mBackendTexture = {}; + } +} + +sk_sp GaneshBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace, + TextureReleaseProc releaseImageProc, + ReleaseContext releaseContext) { + if (mBackendTexture.isValid()) { + mUpdateProc(mImageCtx, mGrContext.get()); + } + + const SkColorType colorType = colorTypeForImage(alphaType); + sk_sp image = + SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin, + colorType, alphaType, toSkColorSpace(dataspace), + releaseImageProc, releaseContext); + if (!image) { + logFatalTexture("Unable to generate SkImage.", dataspace, colorType); + } + return image; +} + +sk_sp GaneshBackendTexture::makeSurface(ui::Dataspace dataspace, + TextureReleaseProc releaseSurfaceProc, + ReleaseContext releaseContext) { + const SkColorType colorType = internalColorType(); + sk_sp surface = + SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture, + kTopLeft_GrSurfaceOrigin, 0, colorType, + toSkColorSpace(dataspace), nullptr, releaseSurfaceProc, + releaseContext); + if (!surface) { + logFatalTexture("Unable to generate SkSurface.", dataspace, colorType); + } + return surface; +} + +void GaneshBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace, + SkColorType colorType) { + switch (mBackendTexture.backend()) { + case GrBackendApi::kOpenGL: { + GrGLTextureInfo textureInfo; + bool retrievedTextureInfo = + GrBackendTextures::GetGLTextureInfo(mBackendTexture, &textureInfo); + LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d" + "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i " + "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u" + " colorType %i", + msg, mBackendTexture.isValid(), static_cast(dataspace), + mBackendTexture.width(), mBackendTexture.height(), + mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(), + static_cast(mBackendTexture.textureType()), retrievedTextureInfo, + textureInfo.fTarget, textureInfo.fFormat, colorType); + break; + } + case GrBackendApi::kVulkan: { + GrVkImageInfo imageInfo; + bool retrievedImageInfo = + GrBackendTextures::GetVkImageInfo(mBackendTexture, &imageInfo); + LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d" + "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i " + "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i " + "fSampleCount: %u fLevelCount: %u colorType %i", + msg, mBackendTexture.isValid(), static_cast(dataspace), + mBackendTexture.width(), mBackendTexture.height(), + mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(), + static_cast(mBackendTexture.textureType()), retrievedImageInfo, + imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount, + colorType); + break; + } + default: + LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg, + static_cast(mBackendTexture.backend())); + break; + } +} + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.h b/libs/renderengine/skia/compat/GaneshBackendTexture.h new file mode 100644 index 0000000000..5cf8647801 --- /dev/null +++ b/libs/renderengine/skia/compat/GaneshBackendTexture.h @@ -0,0 +1,57 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "SkiaBackendTexture.h" +#include "ui/GraphicTypes.h" + +#include +#include +#include + +#include + +namespace android::renderengine::skia { + +class GaneshBackendTexture : public SkiaBackendTexture { +public: + // Creates an internal GrBackendTexture whose contents come from the provided buffer. + GaneshBackendTexture(sk_sp grContext, AHardwareBuffer* buffer, + bool isOutputBuffer); + + ~GaneshBackendTexture() override; + + sk_sp makeImage(SkAlphaType alphaType, ui::Dataspace dataspace, + TextureReleaseProc releaseImageProc, + ReleaseContext releaseContext) override; + + sk_sp makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc, + ReleaseContext releaseContext) override; + +private: + DISALLOW_COPY_AND_ASSIGN(GaneshBackendTexture); + + void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType); + + const sk_sp mGrContext; + GrBackendTexture mBackendTexture; + GrAHardwareBufferUtils::DeleteImageProc mDeleteProc; + GrAHardwareBufferUtils::UpdateImageProc mUpdateProc; + GrAHardwareBufferUtils::TexImageCtx mImageCtx; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp index 51c6a6cd1c..87f00e4e1c 100644 --- a/libs/renderengine/skia/compat/GaneshGpuContext.cpp +++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp @@ -27,8 +27,13 @@ #include #include +#include "../AutoBackendTexture.h" +#include "GaneshBackendTexture.h" +#include "skia/compat/SkiaBackendTexture.h" + #include #include +#include namespace android::renderengine::skia { @@ -66,6 +71,11 @@ sk_sp GaneshGpuContext::grDirectContext() { return mGrContext; } +std::unique_ptr GaneshGpuContext::makeBackendTexture(AHardwareBuffer* buffer, + bool isOutputBuffer) { + return std::make_unique(mGrContext, buffer, isOutputBuffer); +} + sk_sp GaneshGpuContext::createRenderTarget(SkImageInfo imageInfo) { constexpr int kSampleCount = 1; // enable AA constexpr SkSurfaceProps* kProps = nullptr; diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h index 59001eccc2..ec0162d983 100644 --- a/libs/renderengine/skia/compat/GaneshGpuContext.h +++ b/libs/renderengine/skia/compat/GaneshGpuContext.h @@ -29,6 +29,9 @@ public: sk_sp grDirectContext() override; + std::unique_ptr makeBackendTexture(AHardwareBuffer* buffer, + bool isOutputBuffer) override; + sk_sp createRenderTarget(SkImageInfo imageInfo) override; size_t getMaxRenderTargetSize() const override; diff --git a/libs/renderengine/skia/compat/SkiaBackendTexture.h b/libs/renderengine/skia/compat/SkiaBackendTexture.h new file mode 100644 index 0000000000..09877a5ede --- /dev/null +++ b/libs/renderengine/skia/compat/SkiaBackendTexture.h @@ -0,0 +1,85 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace android::renderengine::skia { + +/** + * Abstraction over a Skia backend-specific texture type. + * + * This class does not do any lifecycle management, and should typically be wrapped in an + * AutoBackendTexture::LocalRef. Typically created via SkiaGpuContext::makeBackendTexture(...). + */ +class SkiaBackendTexture { +public: + SkiaBackendTexture(AHardwareBuffer* buffer, bool isOutputBuffer) + : mIsOutputBuffer(isOutputBuffer) { + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(buffer, &desc); + + mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); + } + virtual ~SkiaBackendTexture() = default; + + // These two definitions mirror Skia's own types used for texture release callbacks, which are + // re-declared multiple times between context-specific implementation headers for Ganesh vs. + // Graphite, and within the context of SkImages vs. SkSurfaces. Our own re-declaration allows us + // to not pull in any implementation-specific headers here. + using ReleaseContext = void*; + using TextureReleaseProc = void (*)(ReleaseContext); + + // Guaranteed to be non-null (crashes otherwise). An opaque alphaType may coerce the internal + // color type to RBGX. + virtual sk_sp makeImage(SkAlphaType alphaType, ui::Dataspace dataspace, + TextureReleaseProc releaseImageProc, + ReleaseContext releaseContext) = 0; + + // Guaranteed to be non-null (crashes otherwise). + virtual sk_sp makeSurface(ui::Dataspace dataspace, + TextureReleaseProc releaseSurfaceProc, + ReleaseContext releaseContext) = 0; + + bool isOutputBuffer() const { return mIsOutputBuffer; } + + SkColorType internalColorType() const { return mColorType; } + +protected: + // Strip alpha channel from rawColorType if alphaType is opaque (note: only works for RGBA_8888) + SkColorType colorTypeForImage(SkAlphaType alphaType) const { + if (alphaType == kOpaque_SkAlphaType) { + // TODO: b/40043126 - Support RGBX SkColorType for F16 and support it and 101010x as a + // source + if (internalColorType() == kRGBA_8888_SkColorType) { + return kRGB_888x_SkColorType; + } + } + return internalColorType(); + } + +private: + const bool mIsOutputBuffer; + SkColorType mColorType = kUnknown_SkColorType; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h index ba167f352d..ddf164219d 100644 --- a/libs/renderengine/skia/compat/SkiaGpuContext.h +++ b/libs/renderengine/skia/compat/SkiaGpuContext.h @@ -24,8 +24,12 @@ #include #include +#include "SkiaBackendTexture.h" + #include +#include + namespace android::renderengine::skia { /** @@ -52,6 +56,9 @@ public: LOG_ALWAYS_FATAL("grDirectContext() called on a non-Ganesh instance of SkiaGpuContext!"); } + virtual std::unique_ptr makeBackendTexture(AHardwareBuffer* buffer, + bool isOutputBuffer) = 0; + /** * Notes: * - The surface doesn't count against Skia's caching budgets. -- GitLab From 1cdb2fab7f49df26f6fc43dba849f04186d71778 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 19 Mar 2024 17:21:45 +0000 Subject: [PATCH 104/465] InputTracer: Ensure tracer thread is destructed first InputThread stops when its destructor is called. Initialize it last in ThreadedBackend so that it is the first thing to be destructed. This will guarantee the thread will not access other members that have already been destructed. Bug: 330211703 Change-Id: Ia50585d8276fe87d40d5528b3eb9480a64ea9854 Test: None --- services/inputflinger/dispatcher/trace/ThreadedBackend.cpp | 6 +++--- services/inputflinger/dispatcher/trace/ThreadedBackend.h | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp index b1791b3268..1614789927 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp @@ -38,10 +38,10 @@ struct Visitor : V... { template ThreadedBackend::ThreadedBackend(Backend&& innerBackend) - : mTracerThread( + : mBackend(std::move(innerBackend)), + mTracerThread( "InputTracer", [this]() { threadLoop(); }, - [this]() { mThreadWakeCondition.notify_all(); }), - mBackend(std::move(innerBackend)) {} + [this]() { mThreadWakeCondition.notify_all(); }) {} template ThreadedBackend::~ThreadedBackend() { diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h index cab47af1c1..3bfc72b4d0 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h @@ -44,7 +44,6 @@ public: private: std::mutex mLock; - InputThread mTracerThread; bool mThreadExit GUARDED_BY(mLock){false}; std::condition_variable mThreadWakeCondition; Backend mBackend; @@ -53,6 +52,11 @@ private: TracedEventArgs>; std::vector mQueue GUARDED_BY(mLock); + // InputThread stops when its destructor is called. Initialize it last so that it is the + // first thing to be destructed. This will guarantee the thread will not access other + // members that have already been destructed. + InputThread mTracerThread; + void threadLoop(); }; -- GitLab From 7577a066e32bbf87bbecc574ce7de75f1848ea91 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 19 Mar 2024 18:20:14 +0000 Subject: [PATCH 105/465] InputDevice: switch Sony DualShock 4 to new touchpad stack Bug: 329585708 Test: use the DS4's touchpad over USB and Bluetooth, check it works smoothly (or, as smoothly as it did with the old stack) Test: atest inputflinger_tests Change-Id: Ie57b7112bcaa2306b2b5171707822c46bc9f2c52 --- services/inputflinger/reader/InputDevice.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 2baf576903..4d8ffb68bb 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -509,13 +509,8 @@ std::vector> InputDevice::createMappers( // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); - // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or - // at least load this setting from the IDC file. - const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier(); - const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && - (identifier.product == 0x05c4 || identifier.product == 0x09cc); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && - classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { + classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(createInputMapper(contextPtr, readerConfig)); -- GitLab From bf3c8322208df04f04eca76e193760760e26e7e6 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 23 Feb 2024 02:38:36 +0000 Subject: [PATCH 106/465] InputTracer: Adjust traced event sensitivity based on allow-list An allow-list of packages will be defined in the perfetto config for input traces. We can only allow an event to traced completely (i.e. treated as a non-sensitive event) if _all_ of the UIDs that the event is targeting are allow-listed for the trace. In each trace instace, we maintain a cache of whether UIDs seen so far are allow-listed. Since the allow-list is specified through a list of package names, we must query PackageManager through the InputDispatcher policy to look up the packages that correspond to each UID that we see. Bug: 210460522 Test: manual with perfetto Change-Id: I9c19a5ed941ebc239dccc0363cc6553733e16afd --- .../dispatcher/InputDispatcher.cpp | 9 ++- .../include/InputDispatcherPolicyInterface.h | 3 + .../dispatcher/trace/InputTracer.cpp | 59 ++++++++++++++----- .../dispatcher/trace/InputTracer.h | 2 + .../trace/InputTracingBackendInterface.h | 3 + .../trace/InputTracingPerfettoBackend.cpp | 56 +++++++++++++++++- .../trace/InputTracingPerfettoBackend.h | 12 +++- .../tests/FakeInputDispatcherPolicy.h | 2 + .../tests/InputDispatcher_test.cpp | 2 + 9 files changed, 128 insertions(+), 20 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index dc220fe57e..065c5c873c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -88,12 +88,13 @@ bool isInputTracingEnabled() { } // Create the input tracing backend that writes to perfetto from a single thread. -std::unique_ptr createInputTracingBackendIfEnabled() { +std::unique_ptr createInputTracingBackendIfEnabled( + trace::impl::PerfettoBackend::GetPackageUid getPackageUid) { if (!isInputTracingEnabled()) { return nullptr; } return std::make_unique>( - trace::impl::PerfettoBackend()); + trace::impl::PerfettoBackend(getPackageUid)); } template @@ -891,7 +892,9 @@ private: // --- InputDispatcher --- InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy) - : InputDispatcher(policy, createInputTracingBackendIfEnabled()) {} + : InputDispatcher(policy, createInputTracingBackendIfEnabled([&policy](std::string pkg) { + return policy.getPackageUid(pkg); + })) {} InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, std::unique_ptr traceBackend) diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 62c2b02967..91a3e3f8db 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -163,6 +163,9 @@ public: virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp, const std::set& uids) = 0; + /* Get the UID associated with the given package. */ + virtual gui::Uid getPackageUid(std::string package) = 0; + private: // Additional key latency in case a connection is still processing some motion events. // This will help with the case when a user touched a button that opens a new window, diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index d7762493c3..55ed5c635c 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -19,6 +19,7 @@ #include "InputTracer.h" #include +#include namespace android::inputdispatcher::trace::impl { @@ -71,6 +72,24 @@ inline auto getId(const trace::TracedEvent& v) { return std::visit([](const auto& event) { return event.id; }, v); } +// Helper class to extract relevant information from InputTarget. +struct InputTargetInfo { + gui::Uid uid; + bool isSecureWindow; +}; + +InputTargetInfo getTargetInfo(const InputTarget& target) { + if (target.windowHandle == nullptr) { + if (!target.connection->monitor) { + LOG(FATAL) << __func__ << ": Window is not set for non-monitor target"; + } + // This is a global monitor, assume its target is the system. + return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false}; + } + return {target.windowHandle->getInfo()->ownerUid, + target.windowHandle->getInfo()->layoutParamsFlags.test(gui::WindowInfo::Flag::SECURE)}; +} + } // namespace // --- InputTracer --- @@ -104,19 +123,25 @@ std::unique_ptr InputTracer::createTrackerForSyntheticEve void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, const InputTarget& target) { auto& eventState = getState(cookie); + const InputTargetInfo& targetInfo = getTargetInfo(target); if (eventState->isEventProcessingComplete) { - // TODO(b/210460522): Disallow adding new targets after eventProcessingComplete() is called. + // Disallow adding new targets after eventProcessingComplete() is called. + if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) { + LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete"; + } return; } if (isDerivedCookie(cookie)) { - // TODO(b/210460522): Disallow adding new targets from a derived cookie. + // Disallow adding new targets from a derived cookie. + if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) { + LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie"; + } return; } - if (target.windowHandle != nullptr) { - eventState->isSecure |= target.windowHandle->getInfo()->layoutParamsFlags.test( - gui::WindowInfo::Flag::SECURE); - // TODO(b/210460522): Set events as sensitive when the IME connection is active. - } + + eventState->targets.emplace(targetInfo.uid); + eventState->isSecure |= targetInfo.isSecureWindow; + // TODO(b/210460522): Set events as sensitive when the IME connection is active. } void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { @@ -152,8 +177,9 @@ std::unique_ptr InputTracer::traceDerivedEvent( // is dispatched, such as in the case of key fallback events. To account for these cases, // derived events can be traced after the processing is complete for the original event. const auto& event = eventState->events.back(); - const TracedEventArgs traceArgs{.isSecure = eventState->isSecure}; - writeEventToBackend(event, traceArgs, *mBackend); + const TracedEventArgs traceArgs{.isSecure = eventState->isSecure, + .targets = eventState->targets}; + writeEventToBackend(event, std::move(traceArgs), *mBackend); } return std::make_unique(std::move(eventState), /*isDerived=*/true); } @@ -180,6 +206,10 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, << ": Failed to find a previously traced event that matches the dispatched event"; } + if (eventState->targets.count(dispatchEntry.targetUid) == 0) { + LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting"; + } + // The vsyncId only has meaning if the event is targeting a window. const int32_t windowId = dispatchEntry.windowId.value_or(0); const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0; @@ -196,8 +226,9 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}, resolvedKeyRepeatCount}; if (eventState->isEventProcessingComplete) { - mBackend->traceWindowDispatch(std::move(windowDispatchArgs), - TracedEventArgs{.isSecure = eventState->isSecure}); + const TracedEventArgs traceArgs{.isSecure = eventState->isSecure, + .targets = eventState->targets}; + mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(traceArgs)); } else { eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs)); } @@ -217,7 +248,7 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { void InputTracer::EventState::onEventProcessingComplete() { // Write all of the events known so far to the trace. for (const auto& event : events) { - const TracedEventArgs traceArgs{.isSecure = isSecure}; + const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets}; writeEventToBackend(event, traceArgs, *tracer.mBackend); } // Write all pending dispatch args to the trace. @@ -232,8 +263,8 @@ void InputTracer::EventState::onEventProcessingComplete() { << ": Failed to find a previously traced event that matches the dispatched " "event"; } - const TracedEventArgs traceArgs{.isSecure = isSecure}; - tracer.mBackend->traceWindowDispatch(windowDispatchArgs, traceArgs); + const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets}; + tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(traceArgs)); } pendingDispatchArgs.clear(); diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 4ef6ca61ae..717bc1f1a8 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -66,6 +66,8 @@ private: std::vector pendingDispatchArgs; // True if the event is targeting at least one secure window; bool isSecure{false}; + // The list of all possible UIDs that this event could be targeting. + std::set targets; }; // Get the event state associated with a tracking cookie. diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 4bb5799d72..3ff7fab91f 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -94,6 +95,8 @@ using TracedEvent = std::variant; struct TracedEventArgs { // True if the event is targeting at least one secure window. bool isSecure; + // The list of possible UIDs that this event could be targeting. + std::set targets; }; /** Additional information about an input event being dispatched to a window. */ diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index bed753c730..b76bec31b6 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -22,6 +22,7 @@ #include #include +#include namespace android::inputdispatcher::trace::impl { @@ -29,6 +30,17 @@ namespace { constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent"; +bool isPermanentlyAllowed(gui::Uid uid) { + switch (uid.val()) { + case AID_SYSTEM: + case AID_SHELL: + case AID_ROOT: + return true; + default: + return false; + } +} + } // namespace // --- PerfettoBackend::InputEventDataSource --- @@ -55,6 +67,22 @@ void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::S InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); }); } +void PerfettoBackend::InputEventDataSource::initializeUidMap(GetPackageUid getPackageUid) { + if (mUidMap.has_value()) { + return; + } + + mUidMap = {{}}; + for (const auto& rule : mConfig.rules) { + for (const auto& package : rule.matchAllPackages) { + mUidMap->emplace(package, getPackageUid(package)); + } + for (const auto& package : rule.matchAnyPackages) { + mUidMap->emplace(package, getPackageUid(package)); + } + } +} + bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent( const EventType& type) const { if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS)) { @@ -77,7 +105,6 @@ TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel( return rule.level; } } - // The event is not traced if it matched zero rules. return TraceLevel::TRACE_LEVEL_NONE; } @@ -86,10 +113,31 @@ bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, const TracedEventArgs& args) const { // By default, a rule will match all events. Return early if the rule does not match. + // Match the event if it is directed to a secure window. if (rule.matchSecure.has_value() && *rule.matchSecure != args.isSecure) { return false; } + // Match the event if all of its target packages are explicitly allowed in the "match all" list. + if (!rule.matchAllPackages.empty() && + !std::all_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) { + return isPermanentlyAllowed(uid) || + std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(), + [&](const auto& pkg) { return uid == mUidMap->at(pkg); }); + })) { + return false; + } + + // Match the event if any of its target packages are allowed in the "match any" list. + if (!rule.matchAnyPackages.empty() && + !std::any_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) { + return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(), + [&](const auto& pkg) { return uid == mUidMap->at(pkg); }); + })) { + return false; + } + + // The event matches all matchers specified in the rule. return true; } @@ -99,7 +147,8 @@ std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; std::atomic PerfettoBackend::sNextInstanceId{1}; -PerfettoBackend::PerfettoBackend() { +PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid) + : mGetPackageUid(getPackagesForUid) { // Use a once-flag to ensure that the data source is only registered once per boot, since // we never unregister the InputEventDataSource. std::call_once(sDataSourceRegistrationFlag, []() { @@ -120,6 +169,7 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, const TracedEventArgs& args) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; } @@ -139,6 +189,7 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; } @@ -159,6 +210,7 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs const TracedEventArgs& args) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + dataSource->initializeUidMap(mGetPackageUid); if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) { return; } diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index c4f80c3f0b..af1c6b72e3 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace android::inputdispatcher::trace::impl { @@ -48,7 +49,9 @@ namespace android::inputdispatcher::trace::impl { */ class PerfettoBackend : public InputTracingBackendInterface { public: - PerfettoBackend(); + using GetPackageUid = std::function; + + explicit PerfettoBackend(GetPackageUid); ~PerfettoBackend() override = default; void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override; @@ -66,6 +69,7 @@ private: void OnStart(const StartArgs&) override; void OnStop(const StopArgs&) override; + void initializeUidMap(GetPackageUid); bool shouldIgnoreTracedInputEvent(const EventType&) const; inline ftl::Flags getFlags() const { return mConfig.flags; } TraceLevel resolveTraceLevel(const TracedEventArgs&) const; @@ -75,8 +79,14 @@ private: TraceConfig mConfig; bool ruleMatches(const TraceRule&, const TracedEventArgs&) const; + + std::optional> mUidMap; }; + // TODO(b/330360505): Query the native package manager directly from the data source, + // and remove this. + GetPackageUid mGetPackageUid; + static std::once_flag sDataSourceRegistrationFlag; static std::atomic sNextInstanceId; }; diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index fb2db06aff..e0a73247eb 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -86,6 +86,8 @@ private: void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp, const std::set& uids) override {} + + gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; } }; } // namespace android diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 9375e92eac..fcb5ab0183 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -660,6 +660,8 @@ private: verify(*mFilteredEvent); mFilteredEvent = nullptr; } + + gui::Uid getPackageUid(std::string) override { return gui::Uid::INVALID; } }; } // namespace -- GitLab From c7edaaa0352992ca2d542752e3c37493a4ece953 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 15 Mar 2024 15:31:02 +0000 Subject: [PATCH 107/465] InputTracer: s/TracedEventArgs/TracedEventMetadata Bug: 210460522 Test: atest inputflinger_tests Change-Id: Ibd4f17fd47adf0e2af3a6910a9d81cc7f83043fe --- .../dispatcher/trace/InputTracer.cpp | 26 +++++++++---------- .../trace/InputTracingBackendInterface.h | 8 +++--- .../trace/InputTracingPerfettoBackend.cpp | 25 +++++++++--------- .../trace/InputTracingPerfettoBackend.h | 10 +++---- .../dispatcher/trace/ThreadedBackend.cpp | 12 ++++----- .../dispatcher/trace/ThreadedBackend.h | 8 +++--- .../tests/FakeInputTracingBackend.cpp | 6 ++--- .../tests/FakeInputTracingBackend.h | 7 ++--- 8 files changed, 52 insertions(+), 50 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 55ed5c635c..019283f133 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -61,10 +61,10 @@ TracedEvent createTracedEvent(const KeyEntry& e, EventType type) { e.downTime, e.flags, e.repeatCount, type}; } -void writeEventToBackend(const TracedEvent& event, const TracedEventArgs args, +void writeEventToBackend(const TracedEvent& event, const TracedEventMetadata metadata, InputTracingBackendInterface& backend) { - std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, args); }, - [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, args); }}, + std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, metadata); }, + [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, metadata); }}, event); } @@ -177,9 +177,9 @@ std::unique_ptr InputTracer::traceDerivedEvent( // is dispatched, such as in the case of key fallback events. To account for these cases, // derived events can be traced after the processing is complete for the original event. const auto& event = eventState->events.back(); - const TracedEventArgs traceArgs{.isSecure = eventState->isSecure, - .targets = eventState->targets}; - writeEventToBackend(event, std::move(traceArgs), *mBackend); + const TracedEventMetadata metadata{.isSecure = eventState->isSecure, + .targets = eventState->targets}; + writeEventToBackend(event, std::move(metadata), *mBackend); } return std::make_unique(std::move(eventState), /*isDerived=*/true); } @@ -226,9 +226,9 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}, resolvedKeyRepeatCount}; if (eventState->isEventProcessingComplete) { - const TracedEventArgs traceArgs{.isSecure = eventState->isSecure, - .targets = eventState->targets}; - mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(traceArgs)); + const TracedEventMetadata metadata{.isSecure = eventState->isSecure, + .targets = eventState->targets}; + mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(metadata)); } else { eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs)); } @@ -248,8 +248,8 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { void InputTracer::EventState::onEventProcessingComplete() { // Write all of the events known so far to the trace. for (const auto& event : events) { - const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets}; - writeEventToBackend(event, traceArgs, *tracer.mBackend); + const TracedEventMetadata metadata{.isSecure = isSecure, .targets = targets}; + writeEventToBackend(event, std::move(metadata), *tracer.mBackend); } // Write all pending dispatch args to the trace. for (const auto& windowDispatchArgs : pendingDispatchArgs) { @@ -263,8 +263,8 @@ void InputTracer::EventState::onEventProcessingComplete() { << ": Failed to find a previously traced event that matches the dispatched " "event"; } - const TracedEventArgs traceArgs{.isSecure = isSecure, .targets = targets}; - tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(traceArgs)); + const TracedEventMetadata metadata{.isSecure = isSecure, .targets = targets}; + tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(metadata)); } pendingDispatchArgs.clear(); diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 3ff7fab91f..bc037b39e4 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -92,7 +92,7 @@ struct TracedMotionEvent { using TracedEvent = std::variant; /** Additional information about an input event being traced. */ -struct TracedEventArgs { +struct TracedEventMetadata { // True if the event is targeting at least one secure window. bool isSecure; // The list of possible UIDs that this event could be targeting. @@ -121,13 +121,13 @@ public: virtual ~InputTracingBackendInterface() = default; /** Trace a KeyEvent. */ - virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) = 0; + virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) = 0; /** Trace a MotionEvent. */ - virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) = 0; + virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) = 0; /** Trace an event being sent to a window. */ - virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) = 0; + virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) = 0; }; } // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index b76bec31b6..b413def815 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -98,10 +98,10 @@ bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent( } TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel( - const TracedEventArgs& args) const { + const TracedEventMetadata& metadata) const { // Check for matches with the rules in the order that they are defined. for (const auto& rule : mConfig.rules) { - if (ruleMatches(rule, args)) { + if (ruleMatches(rule, metadata)) { return rule.level; } } @@ -110,17 +110,17 @@ TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel( } bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, - const TracedEventArgs& args) const { + const TracedEventMetadata& metadata) const { // By default, a rule will match all events. Return early if the rule does not match. // Match the event if it is directed to a secure window. - if (rule.matchSecure.has_value() && *rule.matchSecure != args.isSecure) { + if (rule.matchSecure.has_value() && *rule.matchSecure != metadata.isSecure) { return false; } // Match the event if all of its target packages are explicitly allowed in the "match all" list. if (!rule.matchAllPackages.empty() && - !std::all_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) { + !std::all_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) { return isPermanentlyAllowed(uid) || std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(), [&](const auto& pkg) { return uid == mUidMap->at(pkg); }); @@ -130,7 +130,7 @@ bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, // Match the event if any of its target packages are allowed in the "match any" list. if (!rule.matchAnyPackages.empty() && - !std::any_of(args.targets.begin(), args.targets.end(), [&](const auto& uid) { + !std::any_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) { return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(), [&](const auto& pkg) { return uid == mUidMap->at(pkg); }); })) { @@ -166,14 +166,14 @@ PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid) } void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, - const TracedEventArgs& args) { + const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; } - const TraceLevel traceLevel = dataSource->resolveTraceLevel(args); + const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata); if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) { return; } @@ -186,14 +186,15 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, }); } -void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventArgs& args) { +void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, + const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; } - const TraceLevel traceLevel = dataSource->resolveTraceLevel(args); + const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata); if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) { return; } @@ -207,14 +208,14 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEve } void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs, - const TracedEventArgs& args) { + const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); dataSource->initializeUidMap(mGetPackageUid); if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) { return; } - const TraceLevel traceLevel = dataSource->resolveTraceLevel(args); + const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata); if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) { return; } diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index af1c6b72e3..e945066dff 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -54,9 +54,9 @@ public: explicit PerfettoBackend(GetPackageUid); ~PerfettoBackend() override = default; - void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override; - void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override; - void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override; + void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override; + void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override; + void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override; private: // Implementation of the perfetto data source. @@ -72,13 +72,13 @@ private: void initializeUidMap(GetPackageUid); bool shouldIgnoreTracedInputEvent(const EventType&) const; inline ftl::Flags getFlags() const { return mConfig.flags; } - TraceLevel resolveTraceLevel(const TracedEventArgs&) const; + TraceLevel resolveTraceLevel(const TracedEventMetadata&) const; private: const int32_t mInstanceId; TraceConfig mConfig; - bool ruleMatches(const TraceRule&, const TracedEventArgs&) const; + bool ruleMatches(const TraceRule&, const TracedEventMetadata&) const; std::optional> mUidMap; }; diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp index b1791b3268..7e13047b61 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp @@ -54,25 +54,25 @@ ThreadedBackend::~ThreadedBackend() { template void ThreadedBackend::traceMotionEvent(const TracedMotionEvent& event, - const TracedEventArgs& traceArgs) { + const TracedEventMetadata& metadata) { std::scoped_lock lock(mLock); - mQueue.emplace_back(event, traceArgs); + mQueue.emplace_back(event, metadata); mThreadWakeCondition.notify_all(); } template void ThreadedBackend::traceKeyEvent(const TracedKeyEvent& event, - const TracedEventArgs& traceArgs) { + const TracedEventMetadata& metadata) { std::scoped_lock lock(mLock); - mQueue.emplace_back(event, traceArgs); + mQueue.emplace_back(event, metadata); mThreadWakeCondition.notify_all(); } template void ThreadedBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs, - const TracedEventArgs& traceArgs) { + const TracedEventMetadata& metadata) { std::scoped_lock lock(mLock); - mQueue.emplace_back(dispatchArgs, traceArgs); + mQueue.emplace_back(dispatchArgs, metadata); mThreadWakeCondition.notify_all(); } diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h index cab47af1c1..f4659a83a8 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h @@ -38,9 +38,9 @@ public: ThreadedBackend(Backend&& innerBackend); ~ThreadedBackend() override; - void traceKeyEvent(const TracedKeyEvent&, const TracedEventArgs&) override; - void traceMotionEvent(const TracedMotionEvent&, const TracedEventArgs&) override; - void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventArgs&) override; + void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override; + void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override; + void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override; private: std::mutex mLock; @@ -50,7 +50,7 @@ private: Backend mBackend; using TraceEntry = std::pair, - TracedEventArgs>; + TracedEventMetadata>; std::vector mQueue GUARDED_BY(mLock); void threadLoop(); diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp index 069b50d051..b46055ed1c 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.cpp +++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp @@ -162,7 +162,7 @@ base::Result VerifyingTrace::verifyEventTraced(const Event& expectedEvent, // --- FakeInputTracingBackend --- void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event, - const trace::TracedEventArgs&) { + const trace::TracedEventMetadata&) { { std::scoped_lock lock(mTrace->mLock); mTrace->mTracedEvents.emplace(event.id, event); @@ -171,7 +171,7 @@ void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event, } void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event, - const trace::TracedEventArgs&) { + const trace::TracedEventMetadata&) { { std::scoped_lock lock(mTrace->mLock); mTrace->mTracedEvents.emplace(event.id, event); @@ -180,7 +180,7 @@ void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& e } void FakeInputTracingBackend::traceWindowDispatch(const trace::WindowDispatchArgs& args, - const trace::TracedEventArgs&) { + const trace::TracedEventMetadata&) { { std::scoped_lock lock(mTrace->mLock); mTrace->mTracedWindowDispatches.push_back(args); diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h index ab05d6b532..cd4b507384 100644 --- a/services/inputflinger/tests/FakeInputTracingBackend.h +++ b/services/inputflinger/tests/FakeInputTracingBackend.h @@ -82,11 +82,12 @@ public: private: std::shared_ptr mTrace; - void traceKeyEvent(const trace::TracedKeyEvent& entry, const trace::TracedEventArgs&) override; + void traceKeyEvent(const trace::TracedKeyEvent& entry, + const trace::TracedEventMetadata&) override; void traceMotionEvent(const trace::TracedMotionEvent& entry, - const trace::TracedEventArgs&) override; + const trace::TracedEventMetadata&) override; void traceWindowDispatch(const trace::WindowDispatchArgs& entry, - const trace::TracedEventArgs&) override; + const trace::TracedEventMetadata&) override; }; } // namespace android::inputdispatcher -- GitLab From 04a664297d32d0e6d8161d942a131a0efcca2443 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 15 Mar 2024 22:45:27 +0000 Subject: [PATCH 108/465] InputTracer: Adjust sensitivity based on IME connection Based on the trace rules, we can adjust the trace level depending on when there was an active IME connection when the event processing was complete. Bug: 210460522 Test: manual with perfetto Change-Id: I90587c3004fa05517cf44c4c0b6b5c5c40fc00d1 --- .../dispatcher/InputDispatcher.cpp | 7 +++++ .../inputflinger/dispatcher/InputDispatcher.h | 2 ++ .../include/InputDispatcherInterface.h | 5 ++++ .../dispatcher/trace/InputTracer.cpp | 29 ++++++++++++++----- .../dispatcher/trace/InputTracer.h | 5 ++++ .../dispatcher/trace/InputTracerInterface.h | 5 ++++ .../trace/InputTracingBackendInterface.h | 2 ++ .../trace/InputTracingPerfettoBackend.cpp | 6 ++++ 8 files changed, 54 insertions(+), 7 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 065c5c873c..d857723649 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -7144,4 +7144,11 @@ bool InputDispatcher::isPointerInWindow(const sp& token, int32 return false; } +void InputDispatcher::setInputMethodConnectionIsActive(bool isActive) { + std::scoped_lock _l(mLock); + if (mTracer) { + mTracer->setInputMethodConnectionIsActive(isActive); + } +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 13571b3b5f..f884685e19 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -158,6 +158,8 @@ public: bool isPointerInWindow(const sp& token, int32_t displayId, DeviceId deviceId, int32_t pointerId) override; + void setInputMethodConnectionIsActive(bool isActive) override; + private: enum class DropReason { NOT_DROPPED, diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 7c440e8218..6b9262cfa3 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -236,6 +236,11 @@ public: */ virtual bool isPointerInWindow(const sp& token, int32_t displayId, DeviceId deviceId, int32_t pointerId) = 0; + + /* + * Notify the dispatcher that the state of the input method connection changed. + */ + virtual void setInputMethodConnectionIsActive(bool isActive) = 0; }; } // namespace android diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 019283f133..32fa740565 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -141,7 +141,6 @@ void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, eventState->targets.emplace(targetInfo.uid); eventState->isSecure |= targetInfo.isSecureWindow; - // TODO(b/210460522): Set events as sensitive when the IME connection is active. } void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { @@ -177,8 +176,11 @@ std::unique_ptr InputTracer::traceDerivedEvent( // is dispatched, such as in the case of key fallback events. To account for these cases, // derived events can be traced after the processing is complete for the original event. const auto& event = eventState->events.back(); - const TracedEventMetadata metadata{.isSecure = eventState->isSecure, - .targets = eventState->targets}; + const TracedEventMetadata metadata{ + .isSecure = eventState->isSecure, + .targets = eventState->targets, + .isImeConnectionActive = eventState->isImeConnectionActive, + }; writeEventToBackend(event, std::move(metadata), *mBackend); } return std::make_unique(std::move(eventState), /*isDerived=*/true); @@ -226,8 +228,11 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}, resolvedKeyRepeatCount}; if (eventState->isEventProcessingComplete) { - const TracedEventMetadata metadata{.isSecure = eventState->isSecure, - .targets = eventState->targets}; + const TracedEventMetadata metadata{ + .isSecure = eventState->isSecure, + .targets = eventState->targets, + .isImeConnectionActive = eventState->isImeConnectionActive, + }; mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(metadata)); } else { eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs)); @@ -246,9 +251,15 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { // --- InputTracer::EventState --- void InputTracer::EventState::onEventProcessingComplete() { + isImeConnectionActive = tracer.mIsImeConnectionActive; + // Write all of the events known so far to the trace. for (const auto& event : events) { - const TracedEventMetadata metadata{.isSecure = isSecure, .targets = targets}; + const TracedEventMetadata metadata{ + .isSecure = isSecure, + .targets = targets, + .isImeConnectionActive = isImeConnectionActive, + }; writeEventToBackend(event, std::move(metadata), *tracer.mBackend); } // Write all pending dispatch args to the trace. @@ -263,7 +274,11 @@ void InputTracer::EventState::onEventProcessingComplete() { << ": Failed to find a previously traced event that matches the dispatched " "event"; } - const TracedEventMetadata metadata{.isSecure = isSecure, .targets = targets}; + const TracedEventMetadata metadata{ + .isSecure = isSecure, + .targets = targets, + .isImeConnectionActive = isImeConnectionActive, + }; tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(metadata)); } pendingDispatchArgs.clear(); diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index 717bc1f1a8..dfaf7c3146 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -48,9 +48,13 @@ public: std::unique_ptr traceDerivedEvent(const EventEntry&, const EventTrackerInterface&) override; void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override; + void setInputMethodConnectionIsActive(bool isActive) override { + mIsImeConnectionActive = isActive; + } private: std::unique_ptr mBackend; + bool mIsImeConnectionActive{false}; // The state of a tracked event, shared across all events derived from the original event. struct EventState { @@ -68,6 +72,7 @@ private: bool isSecure{false}; // The list of all possible UIDs that this event could be targeting. std::set targets; + bool isImeConnectionActive{false}; }; // Get the event state associated with a tracking cookie. diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h index 609d10c0f7..af6eefb1db 100644 --- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h @@ -103,6 +103,11 @@ public: * provided. */ virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) = 0; + + /** + * Notify that the state of the input method connection changed. + */ + virtual void setInputMethodConnectionIsActive(bool isActive) = 0; }; } // namespace android::inputdispatcher::trace diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index bc037b39e4..2b45e3ae1c 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -97,6 +97,8 @@ struct TracedEventMetadata { bool isSecure; // The list of possible UIDs that this event could be targeting. std::set targets; + // True if the there was an active input method connection while this event was processed. + bool isImeConnectionActive; }; /** Additional information about an input event being dispatched to a window. */ diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index b413def815..9c39743569 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -118,6 +118,12 @@ bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, return false; } + // Match the event if it was processed while there was an active InputMethod connection. + if (rule.matchImeConnectionActive.has_value() && + *rule.matchImeConnectionActive != metadata.isImeConnectionActive) { + return false; + } + // Match the event if all of its target packages are explicitly allowed in the "match all" list. if (!rule.matchAllPackages.empty() && !std::all_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) { -- GitLab From ac63702623f28862b8bec2c5c3fb800568cd207f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 19 Mar 2024 00:02:04 +0000 Subject: [PATCH 109/465] InputTracer: Store Metadata directly in EventState We are essentially storing the metadata in EventState already, so instead of duplicating the fields, store the metadata directly. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I7c2ec30352170772d6f18b429ed121a04afe3dc7 --- .../dispatcher/trace/InputTracer.cpp | 40 +++++-------------- .../dispatcher/trace/InputTracer.h | 7 +--- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 32fa740565..1d4d11c13a 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -126,21 +126,21 @@ void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, const InputTargetInfo& targetInfo = getTargetInfo(target); if (eventState->isEventProcessingComplete) { // Disallow adding new targets after eventProcessingComplete() is called. - if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) { + if (eventState->metadata.targets.count(targetInfo.uid) == 0) { LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete"; } return; } if (isDerivedCookie(cookie)) { // Disallow adding new targets from a derived cookie. - if (eventState->targets.find(targetInfo.uid) == eventState->targets.end()) { + if (eventState->metadata.targets.count(targetInfo.uid) == 0) { LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie"; } return; } - eventState->targets.emplace(targetInfo.uid); - eventState->isSecure |= targetInfo.isSecureWindow; + eventState->metadata.targets.emplace(targetInfo.uid); + eventState->metadata.isSecure |= targetInfo.isSecureWindow; } void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { @@ -176,12 +176,7 @@ std::unique_ptr InputTracer::traceDerivedEvent( // is dispatched, such as in the case of key fallback events. To account for these cases, // derived events can be traced after the processing is complete for the original event. const auto& event = eventState->events.back(); - const TracedEventMetadata metadata{ - .isSecure = eventState->isSecure, - .targets = eventState->targets, - .isImeConnectionActive = eventState->isImeConnectionActive, - }; - writeEventToBackend(event, std::move(metadata), *mBackend); + writeEventToBackend(event, eventState->metadata, *mBackend); } return std::make_unique(std::move(eventState), /*isDerived=*/true); } @@ -208,7 +203,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, << ": Failed to find a previously traced event that matches the dispatched event"; } - if (eventState->targets.count(dispatchEntry.targetUid) == 0) { + if (eventState->metadata.targets.count(dispatchEntry.targetUid) == 0) { LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting"; } @@ -228,12 +223,7 @@ void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry, /*hmac=*/{}, resolvedKeyRepeatCount}; if (eventState->isEventProcessingComplete) { - const TracedEventMetadata metadata{ - .isSecure = eventState->isSecure, - .targets = eventState->targets, - .isImeConnectionActive = eventState->isImeConnectionActive, - }; - mBackend->traceWindowDispatch(std::move(windowDispatchArgs), std::move(metadata)); + mBackend->traceWindowDispatch(std::move(windowDispatchArgs), eventState->metadata); } else { eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs)); } @@ -251,16 +241,11 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { // --- InputTracer::EventState --- void InputTracer::EventState::onEventProcessingComplete() { - isImeConnectionActive = tracer.mIsImeConnectionActive; + metadata.isImeConnectionActive = tracer.mIsImeConnectionActive; // Write all of the events known so far to the trace. for (const auto& event : events) { - const TracedEventMetadata metadata{ - .isSecure = isSecure, - .targets = targets, - .isImeConnectionActive = isImeConnectionActive, - }; - writeEventToBackend(event, std::move(metadata), *tracer.mBackend); + writeEventToBackend(event, metadata, *tracer.mBackend); } // Write all pending dispatch args to the trace. for (const auto& windowDispatchArgs : pendingDispatchArgs) { @@ -274,12 +259,7 @@ void InputTracer::EventState::onEventProcessingComplete() { << ": Failed to find a previously traced event that matches the dispatched " "event"; } - const TracedEventMetadata metadata{ - .isSecure = isSecure, - .targets = targets, - .isImeConnectionActive = isImeConnectionActive, - }; - tracer.mBackend->traceWindowDispatch(windowDispatchArgs, std::move(metadata)); + tracer.mBackend->traceWindowDispatch(windowDispatchArgs, metadata); } pendingDispatchArgs.clear(); diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index dfaf7c3146..ab175bef4a 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -68,11 +68,8 @@ private: bool isEventProcessingComplete{false}; // A queue to hold dispatch args from being traced until event processing is complete. std::vector pendingDispatchArgs; - // True if the event is targeting at least one secure window; - bool isSecure{false}; - // The list of all possible UIDs that this event could be targeting. - std::set targets; - bool isImeConnectionActive{false}; + // The metadata should not be modified after event processing is complete. + TracedEventMetadata metadata{}; }; // Get the event state associated with a tracking cookie. -- GitLab From 52f0abecf3d5d7e9dbd0e4b3824531e346c8f546 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Thu, 14 Mar 2024 18:34:04 -0700 Subject: [PATCH 110/465] Add MRR guarding flag frame_rate_category_mrr This flag will guard new dVRR scheduler features from MRR devices. Bug: 330224639 Test: presubmit Change-Id: Ib180f5fb800de19758362d80d0d3e280d5cf378d --- services/surfaceflinger/common/FlagManager.cpp | 2 ++ .../common/include/common/FlagManager.h | 1 + .../surfaceflinger/surfaceflinger_flags_new.aconfig | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 3b669c6b54..a46966210f 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -115,6 +115,7 @@ void FlagManager::dump(std::string& result) const { /// Trunk stable readonly flags /// DUMP_READ_ONLY_FLAG(connected_display); DUMP_READ_ONLY_FLAG(enable_small_area_detection); + DUMP_READ_ONLY_FLAG(frame_rate_category_mrr); DUMP_READ_ONLY_FLAG(misc1); DUMP_READ_ONLY_FLAG(vrr_config); DUMP_READ_ONLY_FLAG(hotplug2); @@ -197,6 +198,7 @@ FLAG_MANAGER_LEGACY_SERVER_FLAG(use_skia_tracing, PROPERTY_SKIA_ATRACE_ENABLED, /// Trunk stable readonly flags /// FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "") FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "") +FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr") FLAG_MANAGER_READ_ONLY_FLAG(misc1, "") FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config") FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 763963e24f..6ba5a00dbd 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -53,6 +53,7 @@ public: /// Trunk stable readonly flags /// bool connected_display() const; + bool frame_rate_category_mrr() const; bool enable_small_area_detection() const; bool misc1() const; bool vrr_config() const; diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 5451752d91..76bf5169f6 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -10,4 +10,15 @@ flag { bug: "273702768" } # dont_skip_on_early_ro2 +flag { + name: "frame_rate_category_mrr" + namespace: "core_graphics" + description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices" + bug: "330224639" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # frame_rate_category_mrr + # IMPORTANT - please keep alphabetize to reduce merge conflicts -- GitLab From d1a0578ea366e126cbc9e225fec82d9bd0611f38 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Tue, 19 Mar 2024 04:37:38 +0000 Subject: [PATCH 111/465] Add support for --list debug command and fix --latency Fixes: 329247258 Test: presubmit Change-Id: I680207e5ed7e4c7cd9fba9b502b3caeffa415c55 --- services/surfaceflinger/SurfaceFlinger.cpp | 21 ++-- services/surfaceflinger/SurfaceFlinger.h | 23 ++-- services/surfaceflinger/tests/Android.bp | 1 + .../surfaceflinger/tests/Dumpsys_test.cpp | 109 ++++++++++++++++++ 4 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 services/surfaceflinger/tests/Dumpsys_test.cpp diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bf210afe6d..2de8384116 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -6244,9 +6244,9 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) { {"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)}, {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)}, {"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)}, - {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)}, - {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)}, - {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)}, + {"--latency"s, argsMainThreadDumper(&SurfaceFlinger::dumpStats)}, + {"--latency-clear"s, argsMainThreadDumper(&SurfaceFlinger::clearStats)}, + {"--list"s, mainThreadDumper(&SurfaceFlinger::listLayers)}, {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)}, {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)}, {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)}, @@ -6279,28 +6279,29 @@ status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) { return doDump(fd, DumpArgs(), asProto); } -void SurfaceFlinger::listLayersLocked(std::string& result) const { - mCurrentState.traverseInZOrder( - [&](Layer* layer) { StringAppendF(&result, "%s\n", layer->getDebugName()); }); +void SurfaceFlinger::listLayers(std::string& result) const { + for (const auto& layer : mLayerLifecycleManager.getLayers()) { + StringAppendF(&result, "%s\n", layer->getDebugString().c_str()); + } } -void SurfaceFlinger::dumpStatsLocked(const DumpArgs& args, std::string& result) const { +void SurfaceFlinger::dumpStats(const DumpArgs& args, std::string& result) const { StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC()); if (args.size() < 2) return; const auto name = String8(args[1]); - mCurrentState.traverseInZOrder([&](Layer* layer) { + traverseLegacyLayers([&](Layer* layer) { if (layer->getName() == name.c_str()) { layer->dumpFrameStats(result); } }); } -void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) { +void SurfaceFlinger::clearStats(const DumpArgs& args, std::string&) { const bool clearAll = args.size() < 2; const auto name = clearAll ? String8() : String8(args[1]); - mCurrentState.traverse([&](Layer* layer) { + traverseLegacyLayers([&](Layer* layer) { if (clearAll || layer->getName() == name.c_str()) { layer->clearFrameStats(); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 0cc8fbb98a..34a7e7f415 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -509,10 +509,7 @@ private: return lockedDumper(std::bind(dump, this, _1, _2, _3)); } - template >* = nullptr> - Dumper mainThreadDumper(F dump) { - using namespace std::placeholders; - Dumper dumper = std::bind(dump, this, _3); + Dumper mainThreadDumperImpl(Dumper dumper) { return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void { mScheduler ->schedule( @@ -522,6 +519,18 @@ private: }; } + template >* = nullptr> + Dumper mainThreadDumper(F dump) { + using namespace std::placeholders; + return mainThreadDumperImpl(std::bind(dump, this, _3)); + } + + template >* = nullptr> + Dumper argsMainThreadDumper(F dump) { + using namespace std::placeholders; + return mainThreadDumperImpl(std::bind(dump, this, _1, _3)); + } + // Maximum allowed number of display frames that can be set through backdoor static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048; @@ -1113,9 +1122,9 @@ private: void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock); void appendSfConfigString(std::string& result) const; - void listLayersLocked(std::string& result) const; - void dumpStatsLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock); - void clearStatsLocked(const DumpArgs& args, std::string& result); + void listLayers(std::string& result) const; + void dumpStats(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock); + void clearStats(const DumpArgs& args, std::string& result); void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const; void dumpFrameTimeline(const DumpArgs& args, std::string& result) const; void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index 925fe0b794..1fd7ae06c0 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -38,6 +38,7 @@ cc_test { "DereferenceSurfaceControl_test.cpp", "DisplayConfigs_test.cpp", "DisplayEventReceiver_test.cpp", + "Dumpsys_test.cpp", "EffectLayer_test.cpp", "HdrSdrRatioOverlay_test.cpp", "InvalidHandles_test.cpp", diff --git a/services/surfaceflinger/tests/Dumpsys_test.cpp b/services/surfaceflinger/tests/Dumpsys_test.cpp new file mode 100644 index 0000000000..c3914e5422 --- /dev/null +++ b/services/surfaceflinger/tests/Dumpsys_test.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 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. + */ + +#include +#include +#include +#include "android-base/stringprintf.h" +#include "utils/Errors.h" + +namespace android { + +namespace { +status_t runShellCommand(const std::string& cmd, std::string& result) { + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) { + return UNKNOWN_ERROR; + } + + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), pipe) != NULL) { + result += buffer; + } + + pclose(pipe); + return OK; +} +} // namespace + +using android::hardware::graphics::common::V1_1::BufferUsage; + +class WaitForCompletedCallback { +public: + WaitForCompletedCallback() = default; + ~WaitForCompletedCallback() = default; + + static void transactionCompletedCallback(void* callbackContext, nsecs_t /* latchTime */, + const sp& /* presentFence */, + const std::vector& /* stats */) { + ASSERT_NE(callbackContext, nullptr) << "failed to get callback context"; + WaitForCompletedCallback* context = static_cast(callbackContext); + context->notify(); + } + + void wait() { + std::unique_lock lock(mMutex); + cv.wait(lock, [this] { return mCallbackReceived; }); + } + + void notify() { + std::unique_lock lock(mMutex); + mCallbackReceived = true; + cv.notify_one(); + } + +private: + std::mutex mMutex; + std::condition_variable cv; + bool mCallbackReceived = false; +}; + +TEST(Dumpsys, listLayers) { + sp client = sp::make(); + ASSERT_EQ(NO_ERROR, client->initCheck()); + auto newLayer = + client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0); + std::string layersAsString; + EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --list", layersAsString)); + EXPECT_NE(strstr(layersAsString.c_str(), ""), nullptr); +} + +TEST(Dumpsys, stats) { + sp client = sp::make(); + ASSERT_EQ(NO_ERROR, client->initCheck()); + auto newLayer = + client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0); + uint64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | + BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE; + + sp buffer = + sp::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, 1u, usageFlags, "test"); + + WaitForCompletedCallback callback; + SurfaceComposerClient::Transaction() + .setBuffer(newLayer, buffer) + .addTransactionCompletedCallback(WaitForCompletedCallback::transactionCompletedCallback, + &callback) + .apply(); + callback.wait(); + std::string stats; + std::string layerName = base::StringPrintf("MY_TEST_LAYER#%d", newLayer->getLayerId()); + EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency " + layerName, stats)); + EXPECT_NE(std::string(""), stats); + EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency-clear " + layerName, stats)); +} + +} // namespace android -- GitLab From 963da1c0252dab01f65617c9ca08e66ff6596581 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 6 Mar 2024 17:42:39 -0500 Subject: [PATCH 112/465] SF: Match followers' refresh rate to pacesetter's Multi-display refresh rate selection was flawed: The scheduler runs the refresh rate selection algorithm for each display, and then filters the candidate modes of each follower to match the pacesetter's refresh rate. This means that: 1. The followers incorrectly consider refresh rates that don't match the pacesetter. Because the DM-specified constraint is [59, 61] Hz, some situations caused selection of a fractional rate (e.g. 59.94) instead of 60 on certain external displays. The result would be black screens for several seconds due to mode sets if the selection was not stable. 2. The followers incorrectly evaluate heuristics that should only affect the pacesetter, e.g. per-surface votes, global signals. Fix this by teaching RefreshRateSelector about follower displays. Foldables also benefit from no longer running the algorithm twice. Fixes: 324188430 Bug: 329111930 Test: No black screen for 4 seconds upon rotating mirrored YouTube. Test: 60+60 still works on foldables. Test: RefreshRateSelectorTest.pacesetterConsidered Change-Id: Ie1b27e81d860a709c85651f068fedb2b496861de Merged-In: Ie1b27e81d860a709c85651f068fedb2b496861de --- .../Scheduler/RefreshRateSelector.cpp | 34 ++++++++-- .../Scheduler/RefreshRateSelector.h | 23 +++++-- .../surfaceflinger/Scheduler/Scheduler.cpp | 45 ++++++------- services/surfaceflinger/Scheduler/Scheduler.h | 5 ++ .../unittests/RefreshRateSelectorTest.cpp | 65 +++++++++++++------ .../tests/unittests/SchedulerTest.cpp | 26 ++++---- 6 files changed, 128 insertions(+), 70 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index ffd3463296..086842c696 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -474,21 +474,23 @@ float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& lay } auto RefreshRateSelector::getRankedFrameRates(const std::vector& layers, - GlobalSignals signals) const -> RankedFrameRates { + GlobalSignals signals, Fps pacesetterFps) const + -> RankedFrameRates { + GetRankedFrameRatesCache cache{layers, signals, pacesetterFps}; + std::lock_guard lock(mLock); - if (mGetRankedFrameRatesCache && - mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) { + if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) { return mGetRankedFrameRatesCache->result; } - const auto result = getRankedFrameRatesLocked(layers, signals); - mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result}; - return result; + cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps); + mGetRankedFrameRatesCache = std::move(cache); + return mGetRankedFrameRatesCache->result; } auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector& layers, - GlobalSignals signals) const + GlobalSignals signals, Fps pacesetterFps) const -> RankedFrameRates { using namespace fps_approx_ops; ATRACE_CALL(); @@ -496,6 +498,24 @@ auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vectorgetPeakFps() == pacesetterFps; + }); + + if (!ranking.empty()) { + ATRACE_FORMAT_INSTANT("%s (Follower display)", + to_string(ranking.front().frameRateMode.fps).c_str()); + + return {ranking, kNoSignals, pacesetterFps}; + } + + ALOGW("Follower display cannot follow the pacesetter"); + } + // Keep the display at max frame rate for the duration of powering on the display. if (signals.powerOnImminent) { ALOGV("Power On Imminent"); diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 6051e8935d..a5000636ce 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -233,14 +233,18 @@ public: struct RankedFrameRates { FrameRateRanking ranking; // Ordered by descending score. GlobalSignals consideredSignals; + Fps pacesetterFps; bool operator==(const RankedFrameRates& other) const { - return ranking == other.ranking && consideredSignals == other.consideredSignals; + return ranking == other.ranking && consideredSignals == other.consideredSignals && + isApproxEqual(pacesetterFps, other.pacesetterFps); } }; - RankedFrameRates getRankedFrameRates(const std::vector&, GlobalSignals) const - EXCLUDES(mLock); + // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching + // that refresh rate. + RankedFrameRates getRankedFrameRates(const std::vector&, GlobalSignals, + Fps pacesetterFps = {}) const EXCLUDES(mLock); FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) { std::lock_guard lock(mLock); @@ -415,7 +419,8 @@ private: const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock); RankedFrameRates getRankedFrameRatesLocked(const std::vector& layers, - GlobalSignals signals) const REQUIRES(mLock); + GlobalSignals signals, Fps pacesetterFps) const + REQUIRES(mLock); // Returns number of display frames and remainder when dividing the layer refresh period by // display refresh period. @@ -534,8 +539,16 @@ private: Config::FrameRateOverride mFrameRateOverrideConfig; struct GetRankedFrameRatesCache { - std::pair, GlobalSignals> arguments; + std::vector layers; + GlobalSignals signals; + Fps pacesetterFps; + RankedFrameRates result; + + bool matches(const GetRankedFrameRatesCache& other) const { + return layers == other.layers && signals == other.signals && + isApproxEqual(pacesetterFps, other.pacesetterFps); + } }; mutable std::optional mGetRankedFrameRatesCache GUARDED_BY(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 3f9168252b..3b47f4080f 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -1146,38 +1146,31 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { ATRACE_CALL(); - using RankedRefreshRates = RefreshRateSelector::RankedFrameRates; - ui::PhysicalDisplayVector perDisplayRanking; + DisplayModeChoiceMap modeChoices; const auto globalSignals = makeGlobalSignals(); - Fps pacesetterFps; + + const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) { + auto rankedFrameRates = + pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements, + globalSignals); + + const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps; + + modeChoices.try_emplace(*mPacesetterDisplayId, + DisplayModeChoice::from(std::move(rankedFrameRates))); + return pacesetterFps; + }(); for (const auto& [id, display] : mDisplays) { + if (id == *mPacesetterDisplayId) continue; + auto rankedFrameRates = - display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, - globalSignals); - if (id == *mPacesetterDisplayId) { - pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps; - } - perDisplayRanking.push_back(std::move(rankedFrameRates)); - } + display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals, + pacesetterFps); - DisplayModeChoiceMap modeChoices; - using fps_approx_ops::operator==; - - for (auto& [rankings, signals] : perDisplayRanking) { - const auto chosenFrameRateMode = - ftl::find_if(rankings, - [&](const auto& ranking) { - return ranking.frameRateMode.fps == pacesetterFps; - }) - .transform([](const auto& scoredFrameRate) { - return scoredFrameRate.get().frameRateMode; - }) - .value_or(rankings.front().frameRateMode); - - modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(), - DisplayModeChoice{chosenFrameRateMode, signals}); + modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates))); } + return modeChoices; } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 09f75fdaca..2ed3d4c3c4 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -402,6 +402,11 @@ private: DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals) : mode(std::move(mode)), consideredSignals(consideredSignals) {} + static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) { + return {rankedFrameRates.ranking.front().frameRateMode, + rankedFrameRates.consideredSignals}; + } + FrameRateMode mode; GlobalSignals consideredSignals; diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp index 0a6e3054dd..b4460538df 100644 --- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp +++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp @@ -103,8 +103,9 @@ struct TestableRefreshRateSelector : RefreshRateSelector { auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; } auto getRankedFrameRates(const std::vector& layers, - GlobalSignals signals = {}) const { - const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals); + GlobalSignals signals = {}, Fps pacesetterFps = {}) const { + const auto result = + RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps); EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(), ScoredFrameRate::DescendingScore{})); @@ -114,8 +115,8 @@ struct TestableRefreshRateSelector : RefreshRateSelector { auto getRankedRefreshRatesAsPair(const std::vector& layers, GlobalSignals signals) const { - const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals); - return std::make_pair(ranking, consideredSignals); + const auto result = getRankedFrameRates(layers, signals); + return std::make_pair(result.ranking, result.consideredSignals); } FrameRateMode getBestFrameRateMode(const std::vector& layers = {}, @@ -1343,7 +1344,7 @@ TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) { TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { auto selector = createSelector(kModes_60_90, kModeId60); - auto [refreshRates, signals] = selector.getRankedFrameRates({}, {}); + auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {}); EXPECT_FALSE(signals.powerOnImminent); auto expectedRefreshRates = []() -> std::vector { @@ -1427,10 +1428,32 @@ TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) { } } +TEST_P(RefreshRateSelectorTest, pacesetterConsidered) { + auto selector = createSelector(kModes_60_90, kModeId60); + constexpr RefreshRateSelector::GlobalSignals kNoSignals; + + std::vector layers = {{.weight = 1.f}}; + layers[0].vote = LayerVoteType::Min; + + // The pacesetterFps takes precedence over the LayerRequirement. + { + const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz); + EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr); + EXPECT_EQ(kNoSignals, result.consideredSignals); + } + + // The pacesetterFps takes precedence over GlobalSignals. + { + const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz); + EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr); + EXPECT_EQ(kNoSignals, result.consideredSignals); + } +} + TEST_P(RefreshRateSelectorTest, touchConsidered) { auto selector = createSelector(kModes_60_90, kModeId60); - auto [_, signals] = selector.getRankedFrameRates({}, {}); + auto signals = selector.getRankedFrameRates({}, {}).consideredSignals; EXPECT_FALSE(signals.touch); std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true}); @@ -1964,7 +1987,7 @@ TEST_P(RefreshRateSelectorTest, lr.name = "60Hz ExplicitDefault"; lr.focused = true; - const auto [rankedFrameRate, signals] = + const auto [rankedFrameRate, signals, _] = selector.getRankedFrameRates(layers, {.touch = true, .idle = true}); EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60); @@ -2188,7 +2211,7 @@ TEST_P(RefreshRateSelectorTest, EXPECT_EQ(SetPolicyResult::Changed, selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}})); - const auto [ranking, signals] = selector.getRankedFrameRates({}, {}); + const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {}); EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90); EXPECT_FALSE(signals.touch); @@ -2572,7 +2595,7 @@ TEST_P(RefreshRateSelectorTest, idle) { layers[0].vote = voteType; layers[0].desiredRefreshRate = 90_Hz; - const auto [ranking, signals] = + const auto [ranking, signals, _] = selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. @@ -2722,16 +2745,17 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) { auto selector = createSelector(kModes_30_60_72_90_120, kModeId60); using GlobalSignals = RefreshRateSelector::GlobalSignals; - const auto args = std::make_pair(std::vector{}, - GlobalSignals{.touch = true, .idle = true}); - const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{ {90_Hz, kMode90}}}, GlobalSignals{.touch = true}}; - selector.mutableGetRankedRefreshRatesCache() = {args, result}; + selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector{}, + .signals = GlobalSignals{.touch = true, + .idle = true}, + .result = result}; - EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second)); + const auto& cache = *selector.mutableGetRankedRefreshRatesCache(); + EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals)); } TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) { @@ -2739,15 +2763,18 @@ TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) { EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache()); - std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; - RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true}; + const std::vector layers = {{.weight = 1.f}, {.weight = 0.5f}}; + const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true}; + const Fps pacesetterFps = 60_Hz; - const auto result = selector.getRankedFrameRates(layers, globalSignals); + const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps); const auto& cache = selector.mutableGetRankedRefreshRatesCache(); ASSERT_TRUE(cache); - EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals)); + EXPECT_EQ(cache->layers, layers); + EXPECT_EQ(cache->signals, globalSignals); + EXPECT_EQ(cache->pacesetterFps, pacesetterFps); EXPECT_EQ(cache->result, result); } @@ -3674,7 +3701,7 @@ TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) { layers[0].vote = voteType; layers[0].desiredRefreshRate = 90_Hz; - const auto [ranking, signals] = + const auto [ranking, signals, _] = selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true}); // Refresh rate will be chosen by either touch state or idle state. diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 10e2220ece..03c12d6f73 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -356,7 +356,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, FrameRateMode{60_Hz, kDisplay2Mode60}, - globalSignals); + GlobalSignals{}); std::vector layers = {{.weight = 1.f}, {.weight = 1.f}}; @@ -375,7 +375,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, - globalSignals); + GlobalSignals{}); mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals); @@ -394,7 +394,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, - globalSignals); + GlobalSignals{}); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); @@ -416,10 +416,10 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120}, globalSignals)(kDisplayId2, FrameRateMode{120_Hz, kDisplay2Mode120}, - globalSignals)(kDisplayId3, - FrameRateMode{60_Hz, - kDisplay3Mode60}, - globalSignals); + GlobalSignals{})(kDisplayId3, + FrameRateMode{60_Hz, + kDisplay3Mode60}, + GlobalSignals{}); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); @@ -434,12 +434,12 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { expectedChoices = ftl::init::map< const PhysicalDisplayId&, DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60}, - globalSignals)(kDisplayId2, - FrameRateMode{60_Hz, kDisplay2Mode60}, - globalSignals)(kDisplayId3, - FrameRateMode{60_Hz, - kDisplay3Mode60}, - globalSignals); + GlobalSignals{})(kDisplayId2, + FrameRateMode{60_Hz, kDisplay2Mode60}, + GlobalSignals{})(kDisplayId3, + FrameRateMode{60_Hz, + kDisplay3Mode60}, + globalSignals); const auto actualChoices = mScheduler->chooseDisplayModes(); EXPECT_EQ(expectedChoices, actualChoices); -- GitLab From 45681988238f32330f64161a8897c02f6d42b5ed Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Thu, 14 Mar 2024 18:40:15 -0700 Subject: [PATCH 113/465] Add MRR guard logic to SF scheduler Uses a flag to guard the new dVRR scheduler features from MRR devices. This is to easily allow development on dVRR devices and MRR devices separately. Bug: 330224639 Test: atest libsurfaceflinger_unittest Test: atest CtsSurfaceControlTestsStaging Test: dumpsys and observe no category or GTE Change-Id: I8be520b5630c1a8fbde5f0fb2265e803e46983a8 --- .../surfaceflinger/Scheduler/LayerHistory.cpp | 33 ++++++++++++---- .../surfaceflinger/Scheduler/LayerHistory.h | 7 +++- .../surfaceflinger/Scheduler/LayerInfo.cpp | 12 ++++++ services/surfaceflinger/Scheduler/LayerInfo.h | 4 ++ .../Scheduler/RefreshRateSelector.cpp | 10 ++++- .../Scheduler/RefreshRateSelector.h | 5 +++ .../unittests/LayerHistoryIntegrationTest.cpp | 2 + .../tests/unittests/LayerHistoryTest.cpp | 38 +++++++++++++++++++ .../tests/unittests/LayerSnapshotTest.cpp | 8 ++++ 9 files changed, 108 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp index b8d5e76358..974c837139 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.cpp +++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp @@ -40,14 +40,15 @@ namespace android::scheduler { namespace { -bool isLayerActive(const LayerInfo& info, nsecs_t threshold) { +bool isLayerActive(const LayerInfo& info, nsecs_t threshold, bool isVrrDevice) { if (FlagManager::getInstance().misc1() && !info.isVisible()) { return false; } // Layers with an explicit frame rate or frame rate category are kept active, // but ignore NoVote. - if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) { + const auto frameRate = info.getSetFrameRateVote(); + if (frameRate.isValid() && !frameRate.isNoVote() && frameRate.isVoteValidForMrr(isVrrDevice)) { return true; } @@ -194,7 +195,7 @@ auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) - std::lock_guard lock(mLock); - partitionLayers(now); + partitionLayers(now, selector.isVrrDevice()); for (const auto& [key, value] : mActiveLayerInfos) { auto& info = value.second; @@ -236,7 +237,7 @@ auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) - return summary; } -void LayerHistory::partitionLayers(nsecs_t now) { +void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) { ATRACE_CALL(); const nsecs_t threshold = getActiveLayerThreshold(now); @@ -244,7 +245,7 @@ void LayerHistory::partitionLayers(nsecs_t now) { LayerInfos::iterator it = mInactiveLayerInfos.begin(); while (it != mInactiveLayerInfos.end()) { auto& [layerUnsafe, info] = it->second; - if (isLayerActive(*info, threshold)) { + if (isLayerActive(*info, threshold, isVrrDevice)) { // move this to the active map mActiveLayerInfos.insert({it->first, std::move(it->second)}); @@ -262,7 +263,7 @@ void LayerHistory::partitionLayers(nsecs_t now) { it = mActiveLayerInfos.begin(); while (it != mActiveLayerInfos.end()) { auto& [layerUnsafe, info] = it->second; - if (isLayerActive(*info, threshold)) { + if (isLayerActive(*info, threshold, isVrrDevice)) { // Set layer vote if set const auto frameRate = info->getSetFrameRateVote(); @@ -305,7 +306,7 @@ void LayerHistory::partitionLayers(nsecs_t now) { trace(*info, gameFrameRateOverrideVoteType, gameModeFrameRateOverride.getIntValue()); } - } else if (frameRate.isValid()) { + } else if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) { info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate, frameRate.vote.seamlessness, frameRate.category}); if (CC_UNLIKELY(mTraceEnabled)) { @@ -321,14 +322,30 @@ void LayerHistory::partitionLayers(nsecs_t now) { gameDefaultFrameRateOverride.getIntValue()); } } else { + if (frameRate.isValid() && !frameRate.isVoteValidForMrr(isVrrDevice)) { + ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s " + "%s %s", + info->getName().c_str(), + ftl::enum_string(frameRate.vote.type).c_str(), + to_string(frameRate.vote.rate).c_str(), + ftl::enum_string(frameRate.category).c_str()); + } info->resetLayerVote(); } } else { - if (frameRate.isValid()) { + if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) { const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote; info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness, frameRate.category}); } else { + if (!frameRate.isVoteValidForMrr(isVrrDevice)) { + ATRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s " + "%s %s", + info->getName().c_str(), + ftl::enum_string(frameRate.vote.type).c_str(), + to_string(frameRate.vote.rate).c_str(), + ftl::enum_string(frameRate.category).c_str()); + } info->resetLayerVote(); } } diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h index a6f1b56bf2..c09f148a9b 100644 --- a/services/surfaceflinger/Scheduler/LayerHistory.h +++ b/services/surfaceflinger/Scheduler/LayerHistory.h @@ -111,9 +111,12 @@ private: std::string dumpGameFrameRateOverridesLocked() const REQUIRES(mLock); // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive - // layers to mInactiveLayerInfos. + // layers to mInactiveLayerInfos. Layer's active state is determined by multiple factors + // such as update activity, visibility, and frame rate vote. // worst case time complexity is O(2 * inactive + active) - void partitionLayers(nsecs_t now) REQUIRES(mLock); + // now: the current time (system time) when calling the method + // isVrrDevice: true if the device has DisplayMode with VrrConfig specified. + void partitionLayers(nsecs_t now, bool isVrrDevice) REQUIRES(mLock); enum class LayerStatus { NotFound, diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 9745452e89..1bc4ac2f11 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -566,6 +566,18 @@ bool LayerInfo::FrameRate::isValid() const { return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default; } +bool LayerInfo::FrameRate::isVoteValidForMrr(bool isVrrDevice) const { + if (isVrrDevice || FlagManager::getInstance().frame_rate_category_mrr()) { + return true; + } + + if (category == FrameRateCategory::Default && vote.type != FrameRateCompatibility::Gte) { + return true; + } + + return false; +} + std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) { return stream << "{rate=" << rate.vote.rate << " type=" << ftl::enum_string(rate.vote.type) << " seamlessness=" << ftl::enum_string(rate.vote.seamlessness) << '}'; diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h index 326e444815..40903ed7c8 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.h +++ b/services/surfaceflinger/Scheduler/LayerInfo.h @@ -146,6 +146,10 @@ public: // selection. bool isNoVote() const; + // Checks whether the given FrameRate's vote specifications is valid for MRR devices + // given the current flagging. + bool isVoteValidForMrr(bool isVrrDevice) const; + private: static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) { if (!rate.isValid()) { diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index ad59f1a574..ae646a0885 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -1216,6 +1216,8 @@ void RefreshRateSelector::setActiveMode(DisplayModeId modeId, Fps renderFrameRat LOG_ALWAYS_FATAL_IF(!activeModeOpt); mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())}); + mIsVrrDevice = FlagManager::getInstance().vrr_config() && + activeModeOpt->get()->getVrrConfig().has_value(); } RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId, @@ -1429,7 +1431,8 @@ void RefreshRateSelector::constructAvailableRefreshRates() { } return str; }; - ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str()); + ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(), + mIsVrrDevice); return frameRateModes; }; @@ -1438,6 +1441,11 @@ void RefreshRateSelector::constructAvailableRefreshRates() { mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request"); } +bool RefreshRateSelector::isVrrDevice() const { + std::lock_guard lock(mLock); + return mIsVrrDevice; +} + Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const { using namespace fps_approx_ops; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 6051e8935d..a6b02369ad 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -406,6 +406,8 @@ public: std::chrono::milliseconds getIdleTimerTimeout(); + bool isVrrDevice() const; + private: friend struct TestableRefreshRateSelector; @@ -513,6 +515,9 @@ private: std::vector mPrimaryFrameRates GUARDED_BY(mLock); std::vector mAppRequestFrameRates GUARDED_BY(mLock); + // Caches whether the device is VRR-compatible based on the active display mode. + bool mIsVrrDevice GUARDED_BY(mLock) = false; + Policy mDisplayManagerPolicy GUARDED_BY(mLock); std::optional mOverridePolicy GUARDED_BY(mLock); diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp index 110f324c8b..2fb80b146e 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp @@ -274,6 +274,8 @@ TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitExactVote) { } TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true); + createLegacyAndFrontedEndLayer(1); setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH); diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp index 9b8ff42782..c63aaeb487 100644 --- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp @@ -548,7 +548,41 @@ TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) { EXPECT_EQ(0, frequentLayerCount(time)); } +TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false); + + auto layer = createLayer(); + EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); + EXPECT_CALL(*layer, getFrameRateForLayerTree()) + .WillRepeatedly( + Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default, + Seamlessness::OnlySeamless, FrameRateCategory::High))); + + // Set default to Min so it is obvious that the vote reset triggered. + setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min); + + EXPECT_EQ(1, layerCount()); + EXPECT_EQ(0, activeLayerCount()); + + nsecs_t time = systemTime(); + for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) { + history().record(layer->getSequence(), layer->getLayerProps(), time, time, + LayerHistory::LayerUpdateType::Buffer); + time += HI_FPS_PERIOD; + } + + // There is only 1 LayerRequirement due to the disabled flag frame_rate_category_mrr. + ASSERT_EQ(1, summarizeLayerHistory(time).size()); + EXPECT_EQ(1, activeLayerCount()); + EXPECT_EQ(1, frequentLayerCount(time)); + EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote); + EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate); + EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory); +} + TEST_F(LayerHistoryTest, oneLayerExplicitCategory) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true); + auto layer = createLayer(); EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); EXPECT_CALL(*layer, getFrameRateForLayerTree()) @@ -588,6 +622,8 @@ TEST_F(LayerHistoryTest, oneLayerExplicitCategory) { // This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote, // the category is NoPreference. TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true); + auto layer = createLayer(); EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); EXPECT_CALL(*layer, getFrameRateForLayerTree()) @@ -617,6 +653,8 @@ TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) { } TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true); + auto layer = createLayer(); EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true)); EXPECT_CALL(*layer, getFrameRateForLayerTree()) diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 3baa48d002..94989aac84 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include "FrontEnd/LayerHierarchy.h" @@ -26,6 +27,8 @@ #include "LayerHierarchyTest.h" #include "ui/GraphicTypes.h" +#include + #define UPDATE_AND_VERIFY(BUILDER, ...) \ ({ \ SCOPED_TRACE(""); \ @@ -42,6 +45,7 @@ namespace android::surfaceflinger::frontend { using ftl::Flags; using namespace ftl::flag_operators; +using namespace com::android::graphics::surfaceflinger; // To run test: /** @@ -668,6 +672,8 @@ TEST_F(LayerSnapshotTest, translateDataspace) { // This test is similar to "frameRate" test case but checks that the setFrameRateCategory API // interaction also works correctly with the setFrameRate API within SF frontend. TEST_F(LayerSnapshotTest, frameRateWithCategory) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true); + // ROOT // ├── 1 // │ ├── 11 (frame rate set to 244.f) @@ -864,6 +870,8 @@ TEST_F(LayerSnapshotTest, frameRateSelectionStrategy) { } TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) { + SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true); + // ROOT // ├── 1 // │ ├── 11 -- GitLab From e58a92b735a026c2a86346e5963b8e679154e419 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Thu, 29 Feb 2024 14:52:25 -0600 Subject: [PATCH 114/465] Allow root to create secure virtual displays. Bug: 324890339 Test: SurfaceFlinger_test Change-Id: I6b49f3dfc3d66dd7d5e66f99b9947245fc708bc0 --- services/surfaceflinger/SurfaceFlinger.cpp | 6 +++--- services/surfaceflinger/tests/Credentials_test.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index bf210afe6d..5ec9578d82 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -578,11 +578,11 @@ void SurfaceFlinger::run() { sp SurfaceFlinger::createDisplay(const String8& displayName, bool secure, float requestedRefreshRate) { - // onTransact already checks for some permissions, but adding an additional check here. - // This is to ensure that only system and graphics can request to create a secure + // SurfaceComposerAIDL checks for some permissions, but adding an additional check here. + // This is to ensure that only root, system, and graphics can request to create a secure // display. Secure displays can show secure content so we add an additional restriction on it. const int uid = IPCThreadState::self()->getCallingUid(); - if (secure && uid != AID_GRAPHICS && uid != AID_SYSTEM) { + if (secure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) { ALOGE("Only privileged processes can create a secure display"); return nullptr; } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 822ac4d99f..9b83713f50 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -241,7 +241,7 @@ TEST_F(CredentialsTest, CreateDisplayTest) { // Check with root. { UIDFaker f(AID_ROOT); - ASSERT_FALSE(condition()); + ASSERT_TRUE(condition()); } // Check as a Graphics user. -- GitLab From 05cb7647691708ae7318f4dde33aa849fa41d3e6 Mon Sep 17 00:00:00 2001 From: Xiang Wang Date: Tue, 12 Mar 2024 17:27:05 -0700 Subject: [PATCH 115/465] Add ADPF GPU impl flag for SurfaceFlinger Bug: 284324521 Test: atest FlagManagerTest Change-Id: Iaa137969d00aa5cfd50b3c647cdd836e09b9d63e --- services/surfaceflinger/common/FlagManager.cpp | 2 ++ .../surfaceflinger/common/include/common/FlagManager.h | 1 + services/surfaceflinger/surfaceflinger_flags_new.aconfig | 7 +++++++ .../surfaceflinger/tests/unittests/FlagManagerTest.cpp | 2 +- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 3c2ccbc262..6aa7f7a6c8 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -110,6 +110,7 @@ void FlagManager::dump(std::string& result) const { /// Trunk stable server flags /// DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display); + DUMP_SERVER_FLAG(adpf_gpu_sf); DUMP_SERVER_FLAG(adpf_use_fmq_channel); /// Trunk stable readonly flags /// @@ -220,6 +221,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "") /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") +FLAG_MANAGER_SERVER_FLAG(adpf_gpu_sf, "") /// Trunk stable server flags from outside SurfaceFlinger /// FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os) diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 763963e24f..ea3fedba7f 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -49,6 +49,7 @@ public: /// Trunk stable server flags /// bool refresh_rate_overlay_on_external_display() const; + bool adpf_gpu_sf() const; bool adpf_use_fmq_channel() const; /// Trunk stable readonly flags /// diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 5451752d91..9d61fe691a 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -3,6 +3,13 @@ package: "com.android.graphics.surfaceflinger.flags" container: "system" +flag { + name: "adpf_gpu_sf" + namespace: "game" + description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL" + bug: "284324521" +} # adpf_gpu_sf + flag { name: "dont_skip_on_early_ro2" namespace: "core_graphics" diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp index 0adf0b617a..51b5f40a52 100644 --- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp @@ -85,7 +85,7 @@ TEST_F(FlagManagerTest, legacyReturnsValue) { EXPECT_EQ(false, mFlagManager.test_flag()); } -TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) { +TEST_F(FlagManagerTest, crashesIfQueriedBeforeBoot) { mFlagManager.markBootIncomplete(); EXPECT_DEATH(FlagManager::getInstance() .refresh_rate_overlay_on_external_display(), ""); -- GitLab From d6a56c97bf3563194129d6b6f7af03a5683be741 Mon Sep 17 00:00:00 2001 From: Chavi Weingarten Date: Tue, 19 Mar 2024 20:24:14 +0000 Subject: [PATCH 116/465] Addressed NDK API feedback Test: Builds Bug: 330375394 Change-Id: I54a51e3f78d5c44067369d3fce3a61fd18a4157b --- include/android/OWNERS | 4 ++++ include/android/input_transfer_token_jni.h | 8 ++++---- include/android/surface_control_input_receiver.h | 14 +++++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/include/android/OWNERS b/include/android/OWNERS index 38f9c5563a..bc53d7acbe 100644 --- a/include/android/OWNERS +++ b/include/android/OWNERS @@ -1 +1,5 @@ per-file input.h, keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS + +# Window manager +per-file surface_control_input_receiver.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS +per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h index ba5f6f2bdd..92fe9b6921 100644 --- a/include/android/input_transfer_token_jni.h +++ b/include/android/input_transfer_token_jni.h @@ -20,8 +20,9 @@ /** * @file input_transfer_token_jni.h */ -#ifndef ANDROID_INPUT_TRANSFER_TOKEN_JNI_H -#define ANDROID_INPUT_TRANSFER_TOKEN_JNI_H + +#pragma once + #include #include @@ -60,9 +61,8 @@ jobject _Nonnull AInputTransferToken_toJava(JNIEnv* _Nonnull env, * * Available since API level 35. */ -void AInputTransferToken_release(AInputTransferToken* _Nonnull aInputTransferToken) +void AInputTransferToken_release(AInputTransferToken* _Nullable aInputTransferToken) __INTRODUCED_IN(__ANDROID_API_V__); __END_DECLS -#endif // ANDROID_INPUT_TRANSFER_TOKEN_JNI_H /** @} */ diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h index cd2c5dfbcf..bdc52490d5 100644 --- a/include/android/surface_control_input_receiver.h +++ b/include/android/surface_control_input_receiver.h @@ -13,6 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/** + * @addtogroup NativeActivity Native Activity + * @{ + */ +/** + * @file surface_control_input_receiver.h + */ + #pragma once #include @@ -69,7 +77,7 @@ typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__); * other input events will be delivered immediately. * * This is different from AInputReceiver_createUnbatchedInputReceiver in that the input events are - * received batched. The caller must invoke AInputReceiver_release to cleanv up the resources when + * received batched. The caller must invoke AInputReceiver_release to clean up the resources when * no longer needing to use the input receiver. * * \param aChoreographer The AChoreographer used for batching. This should match the @@ -144,7 +152,7 @@ AInputReceiver_getInputTransferToken(AInputReceiver *_Nonnull aInputReceiver) * Available since API level 35. */ void -AInputReceiver_release(AInputReceiver *_Nonnull aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__); +AInputReceiver_release(AInputReceiver *_Nullable aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__); /** * Creates a AInputReceiverCallbacks object that is used when registering for an AInputReceiver. @@ -164,7 +172,7 @@ AInputReceiverCallbacks* _Nonnull AInputReceiverCallbacks_create(void* _Nullable * * Available since API level 35 */ -void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nonnull callbacks) +void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nullable callbacks) __INTRODUCED_IN(__ANDROID_API_V__); /** -- GitLab From 02c160c83ce5ee7d3c736536d5b481742fba83f0 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Mon, 18 Mar 2024 10:40:23 -0400 Subject: [PATCH 117/465] Split Ganesh-specific parts of SkiaVkRenderEngine into subclass Similarly to Ia65306cc825b71fe0b89c7f8545ce1c71a81d86b, this will allow for a Graphite-specific variant of SkiaVkRenderEngine. waitFence and flushAndSubmit are kept on the *RenderEngine classes (as opposed to the recently added SkiaGpuContext abstraction) because they are specific to the intersection of both x , with 3 variants. Their logic is also RE-specific, and not suitable for a Skia abstraction. Misc. cleanup: - Remove SkiaVkRenderEngine's flush() and mBackendContext declarations as they were undefined and unnecessary. - Mark some local variables as const. - Pass named constant to GrDirectContext::wait in GaneshVkRenderEngine::waitFence Test: manual validation & existing tests (refactor) Bug: b/293371537 Change-Id: I695a0554b20ca14cf475aeff1b92c4445c1af55b --- libs/renderengine/Android.bp | 1 + .../skia/GaneshVkRenderEngine.cpp | 84 +++++++++++++++++++ libs/renderengine/skia/GaneshVkRenderEngine.h | 34 ++++++++ libs/renderengine/skia/SkiaRenderEngine.h | 1 + libs/renderengine/skia/SkiaVkRenderEngine.cpp | 59 +------------ libs/renderengine/skia/SkiaVkRenderEngine.h | 17 ++-- 6 files changed, 133 insertions(+), 63 deletions(-) create mode 100644 libs/renderengine/skia/GaneshVkRenderEngine.cpp create mode 100644 libs/renderengine/skia/GaneshVkRenderEngine.h diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 5cd0ce530f..0defc7e0d3 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -83,6 +83,7 @@ filegroup { "skia/AutoBackendTexture.cpp", "skia/Cache.cpp", "skia/ColorSpaces.cpp", + "skia/GaneshVkRenderEngine.cpp", "skia/GLExtensions.cpp", "skia/SkiaRenderEngine.cpp", "skia/SkiaGLRenderEngine.cpp", diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp new file mode 100644 index 0000000000..e76a4c3247 --- /dev/null +++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2024 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. + */ + +#include "GaneshVkRenderEngine.h" + +#undef LOG_TAG +#define LOG_TAG "RenderEngine" + +#include + +#include +#include + +namespace android::renderengine::skia { + +// Ganesh-specific function signature for fFinishedProc callback. +static void unref_semaphore(void* semaphore) { + SkiaVkRenderEngine::DestroySemaphoreInfo* info = + reinterpret_cast(semaphore); + info->unref(); +} + +void GaneshVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) { + if (fenceFd.get() < 0) return; + + const int dupedFd = dup(fenceFd.get()); + if (dupedFd < 0) { + ALOGE("failed to create duplicate fence fd: %d", dupedFd); + sync_wait(fenceFd.get(), -1); + return; + } + + base::unique_fd fenceDup(dupedFd); + VkSemaphore waitSemaphore = + getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release()); + GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore); + constexpr bool kDeleteAfterWait = true; + context->grDirectContext()->wait(1, &beSemaphore, kDeleteAfterWait); +} + +base::unique_fd GaneshVkRenderEngine::flushAndSubmit(SkiaGpuContext* context) { + sk_sp grContext = context->grDirectContext(); + VulkanInterface& vi = getVulkanInterface(isProtected()); + VkSemaphore semaphore = vi.createExportableSemaphore(); + + GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore); + + GrFlushInfo flushInfo; + DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; + if (semaphore != VK_NULL_HANDLE) { + destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore); + flushInfo.fNumSemaphores = 1; + flushInfo.fSignalSemaphores = &backendSemaphore; + flushInfo.fFinishedProc = unref_semaphore; + flushInfo.fFinishedContext = destroySemaphoreInfo; + } + GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); + grContext->submit(GrSyncCpu::kNo); + int drawFenceFd = -1; + if (semaphore != VK_NULL_HANDLE) { + if (GrSemaphoresSubmitted::kYes == submitted) { + drawFenceFd = vi.exportSemaphoreSyncFd(semaphore); + } + // Now that drawFenceFd has been created, we can delete our reference to this semaphore + flushInfo.fFinishedProc(destroySemaphoreInfo); + } + base::unique_fd res(drawFenceFd); + return res; +} + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h new file mode 100644 index 0000000000..90e248718e --- /dev/null +++ b/libs/renderengine/skia/GaneshVkRenderEngine.h @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "skia/SkiaVkRenderEngine.h" + +namespace android::renderengine::skia { + +class GaneshVkRenderEngine : public SkiaVkRenderEngine { + friend std::unique_ptr SkiaVkRenderEngine::create( + const RenderEngineCreationArgs& args); + +protected: + GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {} + + void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; + base::unique_fd flushAndSubmit(SkiaGpuContext* context) override; +}; + +} // namespace android::renderengine::skia diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index 9f892bd0f7..a39adbe522 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -94,6 +94,7 @@ protected: size_t getMaxTextureSize() const override final; size_t getMaxViewportDims() const override final; + // TODO: b/293371537 - Return reference instead of pointer? (Cleanup) SkiaGpuContext* getActiveContext(); bool isProtected() const { return mInProtectedContext; } diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp index d854929960..473fa0a94a 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp @@ -21,6 +21,7 @@ #include "SkiaVkRenderEngine.h" +#include "GaneshVkRenderEngine.h" #include "compat/SkiaGpuContext.h" #include @@ -84,7 +85,8 @@ using base::StringAppendF; std::unique_ptr SkiaVkRenderEngine::create( const RenderEngineCreationArgs& args) { - std::unique_ptr engine(new SkiaVkRenderEngine(args)); + // TODO: b/293371537 - Ganesh vs. Graphite subclass based on flag in RenderEngineCreationArgs + std::unique_ptr engine(new GaneshVkRenderEngine(args)); engine->ensureContextsCreated(); if (sVulkanInterface.isInitialized()) { @@ -129,66 +131,13 @@ bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) { return true; } -static void unref_semaphore(void* semaphore) { - SkiaVkRenderEngine::DestroySemaphoreInfo* info = - reinterpret_cast(semaphore); - info->unref(); -} - -static VulkanInterface& getVulkanInterface(bool protectedContext) { +VulkanInterface& SkiaVkRenderEngine::getVulkanInterface(bool protectedContext) { if (protectedContext) { return sProtectedContentVulkanInterface; } return sVulkanInterface; } -void SkiaVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) { - if (fenceFd.get() < 0) return; - - int dupedFd = dup(fenceFd.get()); - if (dupedFd < 0) { - ALOGE("failed to create duplicate fence fd: %d", dupedFd); - sync_wait(fenceFd.get(), -1); - return; - } - - base::unique_fd fenceDup(dupedFd); - VkSemaphore waitSemaphore = - getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release()); - GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore); - context->grDirectContext()->wait(1, &beSemaphore, true /* delete after wait */); -} - -base::unique_fd SkiaVkRenderEngine::flushAndSubmit(SkiaGpuContext* context) { - sk_sp grContext = context->grDirectContext(); - VulkanInterface& vi = getVulkanInterface(isProtected()); - VkSemaphore semaphore = vi.createExportableSemaphore(); - - GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore); - - GrFlushInfo flushInfo; - DestroySemaphoreInfo* destroySemaphoreInfo = nullptr; - if (semaphore != VK_NULL_HANDLE) { - destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore); - flushInfo.fNumSemaphores = 1; - flushInfo.fSignalSemaphores = &backendSemaphore; - flushInfo.fFinishedProc = unref_semaphore; - flushInfo.fFinishedContext = destroySemaphoreInfo; - } - GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); - grContext->submit(GrSyncCpu::kNo); - int drawFenceFd = -1; - if (semaphore != VK_NULL_HANDLE) { - if (GrSemaphoresSubmitted::kYes == submitted) { - drawFenceFd = vi.exportSemaphoreSyncFd(semaphore); - } - // Now that drawFenceFd has been created, we can delete our reference to this semaphore - flushInfo.fFinishedProc(destroySemaphoreInfo); - } - base::unique_fd res(drawFenceFd); - return res; -} - int SkiaVkRenderEngine::getContextPriority() { // EGL_CONTEXT_PRIORITY_REALTIME_NV constexpr int kRealtimePriority = 0x3357; diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h index 28b7595d41..5fd911f876 100644 --- a/libs/renderengine/skia/SkiaVkRenderEngine.h +++ b/libs/renderengine/skia/SkiaVkRenderEngine.h @@ -71,20 +71,21 @@ public: }; protected: + // Redeclare parent functions that Ganesh vs. Graphite subclasses must implement. + virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override = 0; + virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context) override = 0; + + SkiaVkRenderEngine(const RenderEngineCreationArgs& args); + // Implementations of abstract SkiaRenderEngine functions specific to - // rendering backend + // Vulkan, but shareable between Ganesh and Graphite. SkiaRenderEngine::Contexts createContexts() override; bool supportsProtectedContentImpl() const override; bool useProtectedContextImpl(GrProtected isProtected) override; - void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override; - base::unique_fd flushAndSubmit(SkiaGpuContext* context) override; void appendBackendSpecificInfoToDump(std::string& result) override; -private: - SkiaVkRenderEngine(const RenderEngineCreationArgs& args); - base::unique_fd flush(); - - GrVkBackendContext mBackendContext; + // TODO: b/300533018 - refactor this to be non-static + static VulkanInterface& getVulkanInterface(bool protectedContext); }; } // namespace skia -- GitLab From 793f8366b4f6b116e794bfc93f1a9e5be4e0ab23 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Mon, 8 Jan 2024 20:00:35 +0000 Subject: [PATCH 118/465] Add promise for buffer releaseFence to LayerFE. Buffers can only be released when a release fence fires. Instead of tracking and waiting for each layer's Futures to complete, LayerFE can keep track of its own Future via a promise of a fence. This cleans up the shared futures that are currently being tracked and allow us to setup for eventual goals to reduce the number of screenshot calls to the main thread. Tests using a virtual display are modified to use the mirrorDisplay API with a unique layerstack. Bug: b/294936197, b/285553970 Test: manual (screenshot, camera, rotation, picture-in-picture) Test: presubmit Test: atest CompositionEnginePostCompositionTest Test: atest SurfaceFlinger_test Change-Id: I3a0af2cd02d3d010a1ea7c860c5cb0bc20837ec2 --- .../compositionengine/CompositionEngine.h | 3 + .../include/compositionengine/LayerFE.h | 22 +++ .../impl/CompositionEngine.h | 2 + .../mock/CompositionEngine.h | 1 + .../include/compositionengine/mock/LayerFE.h | 4 + .../src/CompositionEngine.cpp | 29 ++++ .../CompositionEngine/src/Output.cpp | 19 ++- .../tests/CompositionEngineTest.cpp | 32 +++- .../CompositionEngine/tests/OutputTest.cpp | 122 ++++++++++++++ services/surfaceflinger/Layer.cpp | 85 ++++++++-- services/surfaceflinger/Layer.h | 20 ++- services/surfaceflinger/LayerFE.cpp | 27 +++ services/surfaceflinger/LayerFE.h | 8 + services/surfaceflinger/SurfaceFlinger.cpp | 156 +++++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 15 ++ .../TransactionCallbackInvoker.cpp | 13 +- .../TransactionCallbackInvoker.h | 3 +- .../surfaceflinger/common/FlagManager.cpp | 2 + .../common/include/common/FlagManager.h | 1 + .../surfaceflinger_flags_new.aconfig | 11 ++ services/surfaceflinger/tests/Android.bp | 2 + .../tests/LayerCallback_test.cpp | 70 ++++++++ .../tests/MultiDisplayLayerBounds_test.cpp | 41 ++++- .../tests/TransactionTestHarnesses.h | 26 ++- 24 files changed, 644 insertions(+), 70 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h index 7c10fa57ec..e32cc02974 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h @@ -73,6 +73,9 @@ public: // TODO(b/121291683): These will become private/internal virtual void preComposition(CompositionRefreshArgs&) = 0; + // Resolves any unfulfilled promises for release fences + virtual void postComposition(CompositionRefreshArgs&) = 0; + virtual FeatureFlags getFeatureFlags() const = 0; // Debugging diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h index a499928dd0..4e080b356b 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h @@ -133,6 +133,15 @@ public: uint64_t frameNumber = 0; }; + // Describes the states of the release fence. Checking the states allows checks + // to ensure that set_value() is not called on the same promise multiple times, + // and can indicate if the promise has been fulfilled. + enum class ReleaseFencePromiseStatus { + UNINITIALIZED, // Promise not created + INITIALIZED, // Promise created, fence has not been set + FULFILLED // Promise fulfilled, fence is set + }; + // Returns the LayerSettings to pass to RenderEngine::drawLayers. The state may contain shadows // casted by the layer or the content of the layer itself. If the layer does not render then an // empty optional will be returned. @@ -142,6 +151,19 @@ public: // Called after the layer is displayed to update the presentation fence virtual void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack layerStack) = 0; + // Initializes a promise for a buffer release fence and provides the future for that + // fence. This should only be called when a promise has not yet been created, or + // after the previous promise has already been fulfilled. Attempting to call this + // when an existing promise is INITIALIZED will fail because the promise has not + // yet been fulfilled. + virtual ftl::Future createReleaseFenceFuture() = 0; + + // Sets promise with its buffer's release fence + virtual void setReleaseFence(const FenceResult& releaseFence) = 0; + + // Checks if the buffer's release fence has been set + virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0; + // Gets some kind of identifier for the layer for debug purposes. virtual const char* getDebugName() const = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h index c6995576a1..45208dd3c7 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h @@ -48,6 +48,8 @@ public: void preComposition(CompositionRefreshArgs&) override; + void postComposition(CompositionRefreshArgs&) override; + FeatureFlags getFeatureFlags() const override; // Debugging diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h index 9b2387b966..a1b728232c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h @@ -52,6 +52,7 @@ public: MOCK_METHOD1(updateCursorAsync, void(CompositionRefreshArgs&)); MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&)); + MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&)); MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags()); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h index 1b8cc2758f..05a5d3838c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h @@ -20,6 +20,7 @@ #include #include #include +#include "ui/FenceResult.h" namespace android::compositionengine::mock { @@ -52,6 +53,9 @@ public: MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture, ui::LayerStack), (override)); + MOCK_METHOD0(createReleaseFenceFuture, ftl::Future()); + MOCK_METHOD1(setReleaseFence, void(const FenceResult&)); + MOCK_METHOD0(getReleaseFencePromiseStatus, LayerFE::ReleaseFencePromiseStatus()); MOCK_CONST_METHOD0(getDebugName, const char*()); MOCK_CONST_METHOD0(getSequence, int32_t()); MOCK_CONST_METHOD0(hasRoundedCorners, bool()); diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp index b4702085b6..4c776879f0 100644 --- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp +++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp @@ -162,6 +162,7 @@ void CompositionEngine::present(CompositionRefreshArgs& args) { future.get(); } } + postComposition(args); } void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) { @@ -192,6 +193,34 @@ void CompositionEngine::preComposition(CompositionRefreshArgs& args) { mNeedsAnotherUpdate = needsAnotherUpdate; } +// If a buffer is latched but the layer is not presented, such as when +// obscured by another layer, the previous buffer needs to be released. We find +// these buffers and fire a NO_FENCE to release it. This ensures that all +// promises for buffer releases are fulfilled at the end of composition. +void CompositionEngine::postComposition(CompositionRefreshArgs& args) { + if (FlagManager::getInstance().ce_fence_promise()) { + ATRACE_CALL(); + ALOGV(__FUNCTION__); + + for (auto& layerFE : args.layers) { + if (layerFE->getReleaseFencePromiseStatus() == + LayerFE::ReleaseFencePromiseStatus::INITIALIZED) { + layerFE->setReleaseFence(Fence::NO_FENCE); + } + } + + // List of layersWithQueuedFrames does not necessarily overlap with + // list of layers, so those layersWithQueuedFrames also need any + // unfulfilled promises to be resolved for completeness. + for (auto& layerFE : args.layersWithQueuedFrames) { + if (layerFE->getReleaseFencePromiseStatus() == + LayerFE::ReleaseFencePromiseStatus::INITIALIZED) { + layerFE->setReleaseFence(Fence::NO_FENCE); + } + } + } +} + FeatureFlags CompositionEngine::getFeatureFlags() const { return {}; } diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 921e05dfb2..7b5b49d4e3 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -1621,9 +1622,13 @@ void Output::presentFrameAndReleaseLayers() { releaseFence = Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence); } - layer->getLayerFE() - .onLayerDisplayed(ftl::yield(std::move(releaseFence)).share(), - outputState.layerFilter.layerStack); + if (FlagManager::getInstance().ce_fence_promise()) { + layer->getLayerFE().setReleaseFence(releaseFence); + } else { + layer->getLayerFE() + .onLayerDisplayed(ftl::yield(std::move(releaseFence)).share(), + outputState.layerFilter.layerStack); + } } // We've got a list of layers needing fences, that are disjoint with @@ -1631,8 +1636,12 @@ void Output::presentFrameAndReleaseLayers() { // supply them with the present fence. for (auto& weakLayer : mReleasedLayers) { if (const auto layer = weakLayer.promote()) { - layer->onLayerDisplayed(ftl::yield(frame.presentFence).share(), - outputState.layerFilter.layerStack); + if (FlagManager::getInstance().ce_fence_promise()) { + layer->setReleaseFence(frame.presentFence); + } else { + layer->onLayerDisplayed(ftl::yield(frame.presentFence).share(), + outputState.layerFilter.layerStack); + } } } diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp index 042010edd5..639164d8c9 100644 --- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp @@ -28,6 +28,7 @@ #include "MockHWComposer.h" #include "TimeStats/TimeStats.h" +#include "gmock/gmock.h" #include @@ -90,14 +91,16 @@ struct CompositionEnginePresentTest : public CompositionEngineTest { // These are the overridable functions CompositionEngine::present() may // call, and have separate test coverage. MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&)); + MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&)); }; StrictMock mEngine; }; TEST_F(CompositionEnginePresentTest, worksWithEmptyRequest) { - // present() always calls preComposition() + // present() always calls preComposition() and postComposition() EXPECT_CALL(mEngine, preComposition(Ref(mRefreshArgs))); + EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs))); mEngine.present(mRefreshArgs); } @@ -126,6 +129,9 @@ TEST_F(CompositionEnginePresentTest, worksAsExpected) { EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs))) .WillOnce(Return(ftl::yield({}))); + // present() always calls postComposition() + EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs))); + mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3}; mEngine.present(mRefreshArgs); } @@ -481,5 +487,29 @@ TEST_F(CompositionEngineOffloadTest, disabledDisplaysDoNotPreventOthersFromOfflo mEngine.present(mRefreshArgs); } +struct CompositionEnginePostCompositionTest : public CompositionEngineTest { + sp> mLayer1FE = sp>::make(); + sp> mLayer2FE = sp>::make(); + sp> mLayer3FE = sp>::make(); +}; + +TEST_F(CompositionEnginePostCompositionTest, postCompositionReleasesAllFences) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true); + ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise()); + + EXPECT_CALL(*mLayer1FE, getReleaseFencePromiseStatus) + .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED)); + EXPECT_CALL(*mLayer2FE, getReleaseFencePromiseStatus) + .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED)); + EXPECT_CALL(*mLayer3FE, getReleaseFencePromiseStatus) + .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::INITIALIZED)); + mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE}; + + EXPECT_CALL(*mLayer1FE, setReleaseFence(_)).Times(0); + EXPECT_CALL(*mLayer2FE, setReleaseFence(_)).Times(0); + EXPECT_CALL(*mLayer3FE, setReleaseFence(_)).Times(1); + + mEngine.postComposition(mRefreshArgs); +} } // namespace } // namespace android::compositionengine diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 799c7edebd..ea1d8e82fc 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -3164,6 +3164,8 @@ TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCom } TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false); + ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise()); // Simulate getting release fences from each layer, and ensure they are passed to the // front-end layer interface for each layer correctly. @@ -3205,7 +3207,51 @@ TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { mOutput.presentFrameAndReleaseLayers(); } +TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true); + ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise()); + // Simulate getting release fences from each layer, and ensure they are passed to the + // front-end layer interface for each layer correctly. + + mOutput.mState.isEnabled = true; + + // Create three unique fence instances + sp layer1Fence = sp::make(); + sp layer2Fence = sp::make(); + sp layer3Fence = sp::make(); + + Output::FrameFences frameFences; + frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence); + frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence); + frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence); + + EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences)); + EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); + + // Compare the pointers values of each fence to make sure the correct ones + // are passed. This happens to work with the current implementation, but + // would not survive certain calls like Fence::merge() which would return a + // new instance. + EXPECT_CALL(*mLayer1.layerFE, setReleaseFence(_)) + .WillOnce([&layer1Fence](FenceResult releaseFence) { + EXPECT_EQ(FenceResult(layer1Fence), releaseFence); + }); + EXPECT_CALL(*mLayer2.layerFE, setReleaseFence(_)) + .WillOnce([&layer2Fence](FenceResult releaseFence) { + EXPECT_EQ(FenceResult(layer2Fence), releaseFence); + }); + EXPECT_CALL(*mLayer3.layerFE, setReleaseFence(_)) + .WillOnce([&layer3Fence](FenceResult releaseFence) { + EXPECT_EQ(FenceResult(layer3Fence), releaseFence); + }); + + mOutput.presentFrameAndReleaseLayers(); +} + TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false); + ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise()); + mOutput.mState.isEnabled = true; mOutput.mState.usesClientComposition = true; @@ -3228,7 +3274,35 @@ TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) mOutput.presentFrameAndReleaseLayers(); } +TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true); + ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise()); + + mOutput.mState.isEnabled = true; + mOutput.mState.usesClientComposition = true; + + Output::FrameFences frameFences; + frameFences.clientTargetAcquireFence = sp::make(); + frameFences.layerFences.emplace(&mLayer1.hwc2Layer, sp::make()); + frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp::make()); + frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp::make()); + + EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences)); + EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); + + // Fence::merge is called, and since none of the fences are actually valid, + // Fence::NO_FENCE is returned and passed to each setReleaseFence() call. + // This is the best we can do without creating a real kernel fence object. + EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return()); + EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return()); + EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return()); + mOutput.presentFrameAndReleaseLayers(); +} + TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false); + ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise()); + mOutput.mState.isEnabled = true; mOutput.mState.usesClientComposition = true; @@ -3276,6 +3350,54 @@ TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) { EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty()); } +TEST_F(OutputPostFramebufferTest, setReleasedLayersSentPresentFence) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true); + ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise()); + + mOutput.mState.isEnabled = true; + mOutput.mState.usesClientComposition = true; + + // This should happen even if there are no (current) output layers. + EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u)); + + // Load up the released layers with some mock instances + sp> releasedLayer1 = sp>::make(); + sp> releasedLayer2 = sp>::make(); + sp> releasedLayer3 = sp>::make(); + Output::ReleasedLayers layers; + layers.push_back(releasedLayer1); + layers.push_back(releasedLayer2); + layers.push_back(releasedLayer3); + mOutput.setReleasedLayers(std::move(layers)); + + // Set up a fake present fence + sp presentFence = sp::make(); + Output::FrameFences frameFences; + frameFences.presentFence = presentFence; + + EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences)); + EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); + + // Each released layer should be given the presentFence. + EXPECT_CALL(*releasedLayer1, setReleaseFence(_)) + .WillOnce([&presentFence](FenceResult fenceResult) { + EXPECT_EQ(FenceResult(presentFence), fenceResult); + }); + EXPECT_CALL(*releasedLayer2, setReleaseFence(_)) + .WillOnce([&presentFence](FenceResult fenceResult) { + EXPECT_EQ(FenceResult(presentFence), fenceResult); + }); + EXPECT_CALL(*releasedLayer3, setReleaseFence(_)) + .WillOnce([&presentFence](FenceResult fenceResult) { + EXPECT_EQ(FenceResult(presentFence), fenceResult); + }); + + mOutput.presentFrameAndReleaseLayers(); + + // After the call the list of released layers should have been cleared. + EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty()); +} + /* * Output::composeSurfaces() */ diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 736fec6fb2..7e8ed48180 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -15,6 +15,7 @@ */ // TODO(b/129481165): remove the #pragma below and fix conversion issues +#include "TransactionCallbackInvoker.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" @@ -2912,9 +2913,7 @@ void Layer::callReleaseBufferCallback(const sp& l currentMaxAcquiredBufferCount); } -void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult, - ui::LayerStack layerStack, - std::function&& continuation) { +sp Layer::findCallbackHandle() { // If we are displayed on multiple displays in a single composition cycle then we would // need to do careful tracking to enable the use of the mLastClientCompositionFence. // For example we can only use it if all the displays are client comp, and we need @@ -2949,6 +2948,53 @@ void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult, break; } } + return ch; +} + +void Layer::prepareReleaseCallbacks(ftl::Future futureFenceResult, + ui::LayerStack layerStack) { + sp ch = findCallbackHandle(); + + if (ch != nullptr) { + ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; + ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); + ch->name = mName; + } else { + // If we didn't get a release callback yet, e.g. some scenarios when capturing + // screenshots asynchronously, then make sure we don't drop the fence. + mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult)); + std::vector> mergedFences; + sp prevFence = nullptr; + // For a layer that's frequently screenshotted, try to merge fences to make sure we + // don't grow unbounded. + for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) { + auto result = futureReleaseFence.wait_for(0s); + if (result != std::future_status::ready) { + mergedFences.emplace_back(std::move(futureReleaseFence)); + continue; + } + mergeFence(getDebugName(), futureReleaseFence.get().value_or(Fence::NO_FENCE), + prevFence); + } + if (prevFence != nullptr) { + mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence)))); + } + mAdditionalPreviousReleaseFences.swap(mergedFences); + } + + if (mBufferInfo.mBuffer) { + mPreviouslyPresentedLayerStacks.push_back(layerStack); + } + + if (mDrawingState.frameNumber > 0) { + mDrawingState.previousFrameNumber = mDrawingState.frameNumber; + } +} + +void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult, + ui::LayerStack layerStack, + std::function&& continuation) { + sp ch = findCallbackHandle(); if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) { futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share(); @@ -2956,32 +3002,32 @@ void Layer::onLayerDisplayed(ftl::SharedFuture futureFenceResult, if (ch != nullptr) { ch->previousReleaseCallbackId = mPreviousReleaseCallbackId; - ch->previousReleaseFences.emplace_back(std::move(futureFenceResult)); + ch->previousSharedReleaseFences.emplace_back(std::move(futureFenceResult)); ch->name = mName; } else if (FlagManager::getInstance().screenshot_fence_preservation()) { // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots // asynchronously, then make sure we don't drop the fence. - mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult), - std::move(continuation)); + mPreviousReleaseFenceAndContinuations.emplace_back(std::move(futureFenceResult), + std::move(continuation)); std::vector mergedFences; sp prevFence = nullptr; // For a layer that's frequently screenshotted, try to merge fences to make sure we don't // grow unbounded. - for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) { - auto result = futureAndContinution.future.wait_for(0s); + for (const auto& futureAndContinuation : mPreviousReleaseFenceAndContinuations) { + auto result = futureAndContinuation.future.wait_for(0s); if (result != std::future_status::ready) { - mergedFences.emplace_back(futureAndContinution); + mergedFences.emplace_back(futureAndContinuation); continue; } - mergeFence(getDebugName(), futureAndContinution.chain().get().value_or(Fence::NO_FENCE), - prevFence); + mergeFence(getDebugName(), + futureAndContinuation.chain().get().value_or(Fence::NO_FENCE), prevFence); } if (prevFence != nullptr) { mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share()); } - mAdditionalPreviousReleaseFences.swap(mergedFences); + mPreviousReleaseFenceAndContinuations.swap(mergedFences); } if (mBufferInfo.mBuffer) { @@ -3481,16 +3527,23 @@ bool Layer::setTransactionCompletedListeners(const std::vectoracquireTimeOrFence = mCallbackHandleAcquireTimeOrFence; handle->frameNumber = mDrawingState.frameNumber; handle->previousFrameNumber = mDrawingState.previousFrameNumber; - if (FlagManager::getInstance().screenshot_fence_preservation() && + if (FlagManager::getInstance().ce_fence_promise() && mPreviousReleaseBufferEndpoint == handle->listener) { // Add fences from previous screenshots now so that they can be dispatched to the // client. - for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) { - handle->previousReleaseFences.emplace_back(futureAndContinution.chain()); + for (auto& futureReleaseFence : mAdditionalPreviousReleaseFences) { + handle->previousReleaseFences.emplace_back(std::move(futureReleaseFence)); } mAdditionalPreviousReleaseFences.clear(); + } else if (FlagManager::getInstance().screenshot_fence_preservation() && + mPreviousReleaseBufferEndpoint == handle->listener) { + // Add fences from previous screenshots now so that they can be dispatched to the + // client. + for (const auto& futureAndContinution : mPreviousReleaseFenceAndContinuations) { + handle->previousSharedReleaseFences.emplace_back(futureAndContinution.chain()); + } + mPreviousReleaseFenceAndContinuations.clear(); } - // Store so latched time and release fence can be set mDrawingState.callbackHandles.push_back(handle); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 0ceecec7ec..910886904a 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -559,6 +559,14 @@ public: void onLayerDisplayed(ftl::SharedFuture, ui::LayerStack layerStack, std::function&& continuation = nullptr); + // Tracks mLastClientCompositionFence and gets the callback handle for this layer. + sp findCallbackHandle(); + + // Adds the future release fence to a list of fences that are used to release the + // last presented buffer. Also keeps track of the layerstack in a list of previous + // layerstacks that have been presented. + void prepareReleaseCallbacks(ftl::Future, ui::LayerStack layerStack); + void setWasClientComposed(const sp& fence) { mLastClientCompositionFence = fence; mClearClientCompositionFenceOnLayerDisplayed = false; @@ -934,6 +942,7 @@ public: // the release fences from the correct displays when we release the last buffer // from the layer. std::vector mPreviouslyPresentedLayerStacks; + struct FenceAndContinuation { ftl::SharedFuture future; std::function continuation; @@ -946,7 +955,16 @@ public: } } }; - std::vector mAdditionalPreviousReleaseFences; + std::vector mPreviousReleaseFenceAndContinuations; + + // Release fences for buffers that have not yet received a release + // callback. A release callback may not be given when capturing + // screenshots asynchronously. There may be no buffer update for the + // layer, but the layer will still be composited on the screen in every + // frame. Kepping track of these fences ensures that they are not dropped + // and can be dispatched to the client at a later time. + std::vector> mAdditionalPreviousReleaseFences; + // Exposed so SurfaceFlinger can assert that it's held const sp mFlinger; diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp index 43a4397899..620edca4fb 100644 --- a/services/surfaceflinger/LayerFE.cpp +++ b/services/surfaceflinger/LayerFE.cpp @@ -27,6 +27,8 @@ #include "LayerFE.h" #include "SurfaceFlinger.h" +#include "ui/FenceResult.h" +#include "ui/LayerStack.h" namespace android { @@ -387,4 +389,29 @@ const sp LayerFE::getBuffer() const { return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr; } +void LayerFE::setReleaseFence(const FenceResult& releaseFence) { + // Promises should not be fulfilled more than once. This case can occur if virtual + // displays with the same layerstack ID are being created and destroyed in quick + // succession, such as in tests. This would result in a race condition in which + // multiple displays have the same layerstack ID within the same vsync interval. + if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::FULFILLED) { + return; + } + mReleaseFence.set_value(releaseFence); + mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::FULFILLED; +} + +// LayerFEs are reused and a new fence needs to be created whevever a buffer is latched. +ftl::Future LayerFE::createReleaseFenceFuture() { + if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) { + LOG_ALWAYS_FATAL("Attempting to create a new promise while one is still unfulfilled."); + } + mReleaseFence = std::promise(); + mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::INITIALIZED; + return mReleaseFence.get_future(); +} + +LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() { + return mReleaseFencePromiseStatus; +} } // namespace android diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h index 66cb88b20e..019fa22737 100644 --- a/services/surfaceflinger/LayerFE.h +++ b/services/surfaceflinger/LayerFE.h @@ -22,6 +22,9 @@ #include "compositionengine/LayerFE.h" #include "compositionengine/LayerFECompositionState.h" #include "renderengine/LayerSettings.h" +#include "ui/LayerStack.h" + +#include namespace android { @@ -47,6 +50,9 @@ public: std::optional prepareClientComposition( compositionengine::LayerFE::ClientCompositionTargetSettings&) const; CompositionResult&& stealCompositionResult(); + ftl::Future createReleaseFenceFuture() override; + void setReleaseFence(const FenceResult& releaseFence) override; + LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override; std::unique_ptr mSnapshot; @@ -76,6 +82,8 @@ private: CompositionResult mCompositionResult; std::string mName; + std::promise mReleaseFence; + ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED; }; } // namespace android diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9d07671232..3ec5f213e0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -2719,10 +2720,12 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache); - refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size()); - for (auto layer : mLayersWithQueuedFrames) { - if (auto layerFE = layer->getCompositionEngineLayerFE()) - refreshArgs.layersWithQueuedFrames.push_back(layerFE); + if (!FlagManager::getInstance().ce_fence_promise()) { + refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size()); + for (auto& layer : mLayersWithQueuedFrames) { + if (const auto& layerFE = layer->getCompositionEngineLayerFE()) + refreshArgs.layersWithQueuedFrames.push_back(layerFE); + } } refreshArgs.outputColorSetting = mDisplayColorSetting; @@ -2784,22 +2787,56 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( } refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC); - for (auto [layer, layerFE] : layers) { + for (auto& [layer, layerFE] : layers) { layer->onPreComposition(refreshArgs.refreshStartTime); } - mCompositionEngine->present(refreshArgs); - moveSnapshotsFromCompositionArgs(refreshArgs, layers); + if (FlagManager::getInstance().ce_fence_promise()) { + for (auto& [layer, layerFE] : layers) { + attachReleaseFenceFutureToLayer(layer, layerFE, + layerFE->mSnapshot->outputFilter.layerStack); + } + + refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size()); + for (auto& layer : mLayersWithQueuedFrames) { + if (const auto& layerFE = layer->getCompositionEngineLayerFE()) { + refreshArgs.layersWithQueuedFrames.push_back(layerFE); + // Some layers are not displayed and do not yet have a future release fence + if (layerFE->getReleaseFencePromiseStatus() == + LayerFE::ReleaseFencePromiseStatus::UNINITIALIZED || + layerFE->getReleaseFencePromiseStatus() == + LayerFE::ReleaseFencePromiseStatus::FULFILLED) { + // layerStack is invalid because layer is not on a display + attachReleaseFenceFutureToLayer(layer.get(), layerFE.get(), + ui::INVALID_LAYER_STACK); + } + } + } + + mCompositionEngine->present(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); - for (auto [layer, layerFE] : layers) { - CompositionResult compositionResult{layerFE->stealCompositionResult()}; - for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) { - Layer* clonedFrom = layer->getClonedFrom().get(); - auto owningLayer = clonedFrom ? clonedFrom : layer; - owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack); + for (auto& [layer, layerFE] : layers) { + CompositionResult compositionResult{layerFE->stealCompositionResult()}; + if (compositionResult.lastClientCompositionFence) { + layer->setWasClientComposed(compositionResult.lastClientCompositionFence); + } } - if (compositionResult.lastClientCompositionFence) { - layer->setWasClientComposed(compositionResult.lastClientCompositionFence); + + } else { + mCompositionEngine->present(refreshArgs); + moveSnapshotsFromCompositionArgs(refreshArgs, layers); + + for (auto [layer, layerFE] : layers) { + CompositionResult compositionResult{layerFE->stealCompositionResult()}; + for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) { + Layer* clonedFrom = layer->getClonedFrom().get(); + auto owningLayer = clonedFrom ? clonedFrom : layer; + owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack); + } + if (compositionResult.lastClientCompositionFence) { + layer->setWasClientComposed(compositionResult.lastClientCompositionFence); + } } } @@ -3065,8 +3102,13 @@ void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId, auto optDisplay = layerStackToDisplay.get(layerStack); if (optDisplay && !optDisplay->get()->isVirtual()) { auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId()); - layer->onLayerDisplayed(ftl::yield(fence).share(), - ui::INVALID_LAYER_STACK); + if (FlagManager::getInstance().ce_fence_promise()) { + layer->prepareReleaseCallbacks(ftl::yield(fence), + ui::INVALID_LAYER_STACK); + } else { + layer->onLayerDisplayed(ftl::yield(fence).share(), + ui::INVALID_LAYER_STACK); + } } } layer->releasePendingBuffer(presentTime.ns()); @@ -7999,7 +8041,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, // really small crop or frameScale if (reqSize.width <= 0 || reqSize.height <= 0) { - ALOGW("Failed to captureLayes: crop or scale too small"); + ALOGW("Failed to captureLayers: crop or scale too small"); invokeScreenCaptureError(BAD_VALUE, captureListener); return; } @@ -8073,6 +8115,17 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, args.allowProtected, args.grayscale, captureListener); } +// Creates a Future release fence for a layer and keeps track of it in a list to +// release the buffer when the Future is complete. Calls from composittion +// involve needing to refresh the composition start time for stats. +void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, + ui::LayerStack layerStack) { + ftl::Future futureFence = layerFE->createReleaseFenceFuture(); + Layer* clonedFrom = layer->getClonedFrom().get(); + auto owningLayer = clonedFrom ? clonedFrom : layer; + owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack); +} + bool SurfaceFlinger::layersHasProtectedLayer( const std::vector>>& layers) const { bool protectedLayerFound = false; @@ -8108,7 +8161,6 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture, const bool supportsProtected = getRenderEngine().supportsProtectedContent(); bool hasProtectedLayer = false; if (allowProtected && supportsProtected) { - // Snapshots must be taken from the main thread. auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get(); hasProtectedLayer = layersHasProtectedLayer(layers); } @@ -8149,6 +8201,14 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( auto takeScreenshotFn = [=, this, renderAreaFuture = std::move(renderAreaFuture)]() REQUIRES( kMainThreadContext) mutable -> ftl::SharedFuture { + // LayerSnapshots must be obtained from the main thread. + auto layers = getLayerSnapshots(); + if (FlagManager::getInstance().ce_fence_promise()) { + for (auto& [layer, layerFE] : layers) { + attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); + } + } + ScreenCaptureResults captureResults; std::shared_ptr renderArea = renderAreaFuture.get(); if (!renderArea) { @@ -8162,8 +8222,8 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( ftl::SharedFuture renderFuture; renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) { - renderFuture = renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling, - grayscale, isProtected, captureResults); + renderFuture = renderScreenImpl(renderArea, buffer, regionSampling, grayscale, + isProtected, captureResults, layers); }); if (captureListener) { @@ -8180,6 +8240,10 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( return renderFuture; }; + // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number + // of calls on the main thread. renderAreaFuture runs on the main thread and should + // no longer be a future, so that it does not need to make an additional jump on the + // main thread whenever get() is called. auto future = mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn))); @@ -8195,9 +8259,17 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( std::shared_ptr renderArea, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) { - ATRACE_CALL(); - auto layers = getLayerSnapshots(); + return renderScreenImpl(renderArea, buffer, regionSampling, grayscale, isProtected, + captureResults, layers); +} + +ftl::SharedFuture SurfaceFlinger::renderScreenImpl( + std::shared_ptr renderArea, + const std::shared_ptr& buffer, bool regionSampling, + bool grayscale, bool isProtected, ScreenCaptureResults& captureResults, + std::vector>>& layers) { + ATRACE_CALL(); for (auto& [_, layerFE] : layers) { frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get(); @@ -8351,24 +8423,26 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share() : ftl::yield(present()).share(); - for (auto& [layer, layerFE] : layers) { - layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK, - [layerFE = std::move(layerFE)](FenceResult) { - if (FlagManager::getInstance() - .screenshot_fence_preservation()) { - const auto compositionResult = - layerFE->stealCompositionResult(); - const auto& fences = compositionResult.releaseFences; - // CompositionEngine may choose to cull layers that - // aren't visible, so pass a non-fence. - return fences.empty() ? Fence::NO_FENCE - : fences.back().first.get(); - } else { - return layerFE->stealCompositionResult() - .releaseFences.back() - .first.get(); - } - }); + if (!FlagManager::getInstance().ce_fence_promise()) { + for (auto& [layer, layerFE] : layers) { + layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK, + [layerFE = std::move(layerFE)](FenceResult) { + if (FlagManager::getInstance() + .screenshot_fence_preservation()) { + const auto compositionResult = + layerFE->stealCompositionResult(); + const auto& fences = compositionResult.releaseFences; + // CompositionEngine may choose to cull layers that + // aren't visible, so pass a non-fence. + return fences.empty() ? Fence::NO_FENCE + : fences.back().first.get(); + } else { + return layerFE->stealCompositionResult() + .releaseFences.back() + .first.get(); + } + }); + } } return presentFuture; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 34a7e7f415..c106abda37 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -881,6 +881,11 @@ private: // Traverse through all the layers and compute and cache its bounds. void computeLayerBounds(); + // Creates a promise for a future release fence for a layer. This allows for + // the layer to keep track of when its buffer can be released. + void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack); + + // Checks if a protected layer exists in a list of layers. bool layersHasProtectedLayer(const std::vector>>& layers) const; void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize, @@ -892,12 +897,22 @@ private: const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, const sp&); + // Overloaded version of renderScreenImpl that is used when layer snapshots have + // not yet been captured, and thus cannot yet be passed in as a parameter. + // Needed for TestableSurfaceFlinger. ftl::SharedFuture renderScreenImpl( std::shared_ptr, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + ftl::SharedFuture renderScreenImpl( + std::shared_ptr, + const std::shared_ptr&, bool regionSampling, + bool grayscale, bool isProtected, ScreenCaptureResults&, + std::vector>>& layers) EXCLUDES(mStateLock) + REQUIRES(kMainThreadContext); + // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a // matching ownerUid void traverseLayersInLayerStack(ui::LayerStack, const int32_t uid, diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp index 7b5298c82e..222ae30acb 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.cpp +++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp @@ -30,6 +30,7 @@ #include #include +#include #include namespace android { @@ -128,9 +129,17 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp& sp surfaceControl = handle->surfaceControl.promote(); if (surfaceControl) { sp prevFence = nullptr; - for (const auto& future : handle->previousReleaseFences) { - mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence); + + if (FlagManager::getInstance().ce_fence_promise()) { + for (auto& future : handle->previousReleaseFences) { + mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence); + } + } else { + for (const auto& future : handle->previousSharedReleaseFences) { + mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence); + } } + handle->previousReleaseFence = prevFence; handle->previousReleaseFences.clear(); diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h index 245398f2f4..cb7150b943 100644 --- a/services/surfaceflinger/TransactionCallbackInvoker.h +++ b/services/surfaceflinger/TransactionCallbackInvoker.h @@ -46,7 +46,8 @@ public: bool releasePreviousBuffer = false; std::string name; sp previousReleaseFence; - std::vector> previousReleaseFences; + std::vector> previousReleaseFences; + std::vector> previousSharedReleaseFences; std::variant> acquireTimeOrFence = -1; nsecs_t latchTime = -1; std::optional transformHint = std::nullopt; diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 8276eedcfc..4b34a55648 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -136,6 +136,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(restore_blur_step); DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro); DUMP_READ_ONLY_FLAG(protected_if_client); + DUMP_READ_ONLY_FLAG(ce_fence_promise); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG #undef DUMP_FLAG_INTERVAL @@ -220,6 +221,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "") FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step") FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "") FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "") +FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index b55bd98372..320e34bf08 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -75,6 +75,7 @@ public: bool restore_blur_step() const; bool dont_skip_on_early_ro() const; bool protected_if_client() const; + bool ce_fence_promise() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index e7bfb6c960..0a5cde33a5 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -10,6 +10,17 @@ flag { bug: "284324521" } # adpf_gpu_sf +flag { + name: "ce_fence_promise" + namespace: "window_surfaces" + description: "Moves logic for buffer release fences into LayerFE" + bug: "294936197" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "dont_skip_on_early_ro2" namespace: "core_graphics" diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index 1fd7ae06c0..d60ef48690 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -68,6 +68,7 @@ cc_test { static_libs: [ "liblayers_proto", "android.hardware.graphics.composer@2.1", + "libsurfaceflinger_common", ], shared_libs: [ "android.hardware.graphics.common@1.2", @@ -83,6 +84,7 @@ cc_test { "libprotobuf-cpp-full", "libui", "libutils", + "server_configurable_flags", ], header_libs: [ "libnativewindow_headers", diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp index 79886bde45..b4496d3f1a 100644 --- a/services/surfaceflinger/tests/LayerCallback_test.cpp +++ b/services/surfaceflinger/tests/LayerCallback_test.cpp @@ -1295,4 +1295,74 @@ TEST_F(LayerCallbackTest, SetNullBufferOnLayerWithoutBuffer) { } } +TEST_F(LayerCallbackTest, OccludedLayerHasReleaseCallback) { + sp layer1, layer2; + ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer()); + ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer()); + + Transaction transaction1, transaction2; + CallbackHelper callback1a, callback1b, callback2a, callback2b; + int err = fillTransaction(transaction1, &callback1a, layer1); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + err = fillTransaction(transaction2, &callback2a, layer2); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + + ui::Size bufferSize = getBufferSize(); + + // Occlude layer1 with layer2 + TransactionUtils::setFrame(transaction1, layer1, + Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32)); + TransactionUtils::setFrame(transaction2, layer2, + Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32)); + transaction1.apply(); + transaction2.apply(); + + ExpectedResult expected1a, expected1b, expected2a, expected2b; + expected1a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1}, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + + expected2a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2}, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::NOT_RELEASED); + + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1a, expected1a, true)); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2a, expected2a, true)); + + // Submit new buffers so previous buffers can be released + err = fillTransaction(transaction1, &callback1b, layer1); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + err = fillTransaction(transaction2, &callback2b, layer2); + if (err) { + GTEST_SUCCEED() << "test not supported"; + return; + } + + TransactionUtils::setFrame(transaction1, layer1, + Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32)); + TransactionUtils::setFrame(transaction2, layer2, + Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32)); + transaction1.apply(); + transaction2.apply(); + + expected1b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1}, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::RELEASED); + + expected2b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2}, + ExpectedResult::Buffer::ACQUIRED, + ExpectedResult::PreviousBuffer::RELEASED); + + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1b, expected1b, true)); + EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2b, expected2b, true)); +} } // namespace android diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp index 15ff696412..7fce7e9af9 100644 --- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp +++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp @@ -18,9 +18,12 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" +#include #include #include "LayerTransactionTest.h" +#include "gui/SurfaceComposerClient.h" +#include "ui/DisplayId.h" namespace android { @@ -37,7 +40,8 @@ protected: const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); ASSERT_FALSE(ids.empty()); - mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); + mMainDisplayId = ids.front(); + mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(mMainDisplayId); SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState); SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode); @@ -85,6 +89,7 @@ protected: ui::DisplayState mMainDisplayState; ui::DisplayMode mMainDisplayMode; sp mMainDisplay; + PhysicalDisplayId mMainDisplayId; sp mVirtualDisplay; sp mProducer; sp mColorLayer; @@ -119,6 +124,9 @@ TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInMirroredVirtualDisplay) { createDisplay(mMainDisplayState.layerStackSpaceRect, ui::DEFAULT_LAYER_STACK); createColorLayer(ui::DEFAULT_LAYER_STACK); + sp mirrorSc = + SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId); + asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); }); // Verify color layer renders correctly on main display and it is mirrored on the @@ -133,6 +141,37 @@ TEST_F(MultiDisplayLayerBoundsTest, RenderLayerInMirroredVirtualDisplay) { sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255}); } +TEST_F(MultiDisplayLayerBoundsTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) { + // Create a display and use a unique layerstack ID for mirrorDisplay() so + // the contents of the main display are mirrored on to the virtual display. + + // A unique layerstack ID must be used because sharing the same layerFE + // with more than one display is unsupported. A unique layerstack ensures + // that a different layerFE is used between displays. + constexpr ui::LayerStack layerStack{77687666}; // ASCII for MDLB (MultiDisplayLayerBounds) + createDisplay(mMainDisplayState.layerStackSpaceRect, layerStack); + createColorLayer(ui::DEFAULT_LAYER_STACK); + + sp mirrorSc = + SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId); + + asTransaction([&](Transaction& t) { + t.setPosition(mColorLayer, 10, 10); + t.setLayerStack(mirrorSc, layerStack); + }); + + // Verify color layer renders correctly on main display and it is mirrored on the + // virtual display. + std::unique_ptr sc; + ScreenCapture::captureScreen(&sc, mMainDisplay); + sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor); + sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255}); + + ScreenCapture::captureScreen(&sc, mVirtualDisplay); + sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor); + sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255}); +} + } // namespace android // TODO(b/129481165): remove the #pragma below and fix conversion issues diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h index 797a64c7d7..87e6d3ee9f 100644 --- a/services/surfaceflinger/tests/TransactionTestHarnesses.h +++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h @@ -16,9 +16,11 @@ #ifndef ANDROID_TRANSACTION_TEST_HARNESSES #define ANDROID_TRANSACTION_TEST_HARNESSES +#include #include #include "LayerTransactionTest.h" +#include "ui/LayerStack.h" namespace android { @@ -36,9 +38,10 @@ public: case RenderPath::VIRTUAL_DISPLAY: const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); + const PhysicalDisplayId displayId = ids.front(); const auto displayToken = ids.empty() ? nullptr - : SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); + : SurfaceComposerClient::getPhysicalDisplayToken(displayId); ui::DisplayState displayState; SurfaceComposerClient::getDisplayState(displayToken, &displayState); @@ -66,11 +69,21 @@ public: vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/); + constexpr ui::LayerStack layerStack{ + 848472}; // ASCII for TTH (TransactionTestHarnesses) + sp mirrorSc = + SurfaceComposerClient::getDefault()->mirrorDisplay(displayId); + SurfaceComposerClient::Transaction t; t.setDisplaySurface(vDisplay, producer); - t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK); t.setDisplayProjection(vDisplay, displayState.orientation, Rect(displayState.layerStackSpaceRect), Rect(resolution)); + if (FlagManager::getInstance().ce_fence_promise()) { + t.setDisplayLayerStack(vDisplay, layerStack); + t.setLayerStack(mirrorSc, layerStack); + } else { + t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK); + } t.apply(); SurfaceComposerClient::Transaction().apply(true); @@ -85,6 +98,15 @@ public: constexpr bool kContainsHdr = false; auto sc = std::make_unique(item.mGraphicBuffer, kContainsHdr); itemConsumer->releaseBuffer(item); + + // Possible race condition with destroying virtual displays, in which + // CompositionEngine::present may attempt to be called on the same + // display multiple times. The layerStack is set to invalid here so + // that the display is ignored if that scenario occurs. + if (FlagManager::getInstance().ce_fence_promise()) { + t.setLayerStack(mirrorSc, ui::INVALID_LAYER_STACK); + t.apply(true); + } SurfaceComposerClient::destroyDisplay(vDisplay); return sc; } -- GitLab From bbf362d24642ed22fd7d19f428125c8bd9e3f170 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Wed, 20 Mar 2024 21:59:06 +0000 Subject: [PATCH 119/465] Delete border rendering code from SurfaceFlinger. Removed code is never used and drawing borders is done instead by Window Manager Service. Changes revert ag/16980603 and ag/17496275. Bug: b/227656283 Test: presubmit Test: SurfaceFlinger_test Change-Id: Ib5c8bf74ad6764d65536dc60cc3c458edde86b3f --- libs/gui/LayerState.cpp | 25 -- libs/gui/SurfaceComposerClient.cpp | 17 -- libs/gui/include/gui/LayerState.h | 10 +- libs/gui/include/gui/SurfaceComposerClient.h | 3 - .../include/renderengine/BorderRenderInfo.h | 36 --- .../include/renderengine/DisplaySettings.h | 6 +- libs/renderengine/skia/SkiaRenderEngine.cpp | 19 -- libs/renderengine/tests/RenderEngineTest.cpp | 45 --- .../CompositionRefreshArgs.h | 8 - .../include/compositionengine/impl/Output.h | 1 - .../impl/OutputCompositionState.h | 3 - .../CompositionEngine/src/Output.cpp | 45 --- .../src/OutputCompositionState.cpp | 5 - services/surfaceflinger/Layer.cpp | 23 -- services/surfaceflinger/Layer.h | 8 - services/surfaceflinger/SurfaceFlinger.cpp | 24 -- services/surfaceflinger/tests/Android.bp | 1 - .../surfaceflinger/tests/LayerBorder_test.cpp | 287 ------------------ .../surfaceflinger/tests/utils/ColorUtils.h | 12 - 19 files changed, 3 insertions(+), 575 deletions(-) delete mode 100644 libs/renderengine/include/renderengine/BorderRenderInfo.h delete mode 100644 services/surfaceflinger/tests/LayerBorder_test.cpp diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 1e0aacddab..0a2879975b 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -90,7 +90,6 @@ layer_state_t::layer_state_t() fixedTransformHint(ui::Transform::ROT_INVALID), autoRefresh(false), isTrustedOverlay(false), - borderEnabled(false), bufferCrop(Rect::INVALID_RECT), destinationFrame(Rect::INVALID_RECT), dropInputMode(gui::DropInputMode::NONE) { @@ -122,12 +121,6 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.write, transparentRegion); SAFE_PARCEL(output.writeUint32, bufferTransform); SAFE_PARCEL(output.writeBool, transformToDisplayInverse); - SAFE_PARCEL(output.writeBool, borderEnabled); - SAFE_PARCEL(output.writeFloat, borderWidth); - SAFE_PARCEL(output.writeFloat, borderColor.r); - SAFE_PARCEL(output.writeFloat, borderColor.g); - SAFE_PARCEL(output.writeFloat, borderColor.b); - SAFE_PARCEL(output.writeFloat, borderColor.a); SAFE_PARCEL(output.writeUint32, static_cast(dataspace)); SAFE_PARCEL(output.write, hdrMetadata); SAFE_PARCEL(output.write, surfaceDamageRegion); @@ -238,17 +231,6 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.read, transparentRegion); SAFE_PARCEL(input.readUint32, &bufferTransform); SAFE_PARCEL(input.readBool, &transformToDisplayInverse); - SAFE_PARCEL(input.readBool, &borderEnabled); - SAFE_PARCEL(input.readFloat, &tmpFloat); - borderWidth = tmpFloat; - SAFE_PARCEL(input.readFloat, &tmpFloat); - borderColor.r = tmpFloat; - SAFE_PARCEL(input.readFloat, &tmpFloat); - borderColor.g = tmpFloat; - SAFE_PARCEL(input.readFloat, &tmpFloat); - borderColor.b = tmpFloat; - SAFE_PARCEL(input.readFloat, &tmpFloat); - borderColor.a = tmpFloat; uint32_t tmpUint32 = 0; SAFE_PARCEL(input.readUint32, &tmpUint32); @@ -659,12 +641,6 @@ void layer_state_t::merge(const layer_state_t& other) { what |= eShadowRadiusChanged; shadowRadius = other.shadowRadius; } - if (other.what & eRenderBorderChanged) { - what |= eRenderBorderChanged; - borderEnabled = other.borderEnabled; - borderWidth = other.borderWidth; - borderColor = other.borderColor; - } if (other.what & eDefaultFrameRateCompatibilityChanged) { what |= eDefaultFrameRateCompatibilityChanged; defaultFrameRateCompatibility = other.defaultFrameRateCompatibility; @@ -794,7 +770,6 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace); if (other.what & eMetadataChanged) diff |= eMetadataChanged; CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius); - CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor); CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility); CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority); CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility, diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 4f1356bebb..1d2ea3ea02 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2250,23 +2250,6 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDropI return *this; } -SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder( - const sp& sc, bool shouldEnable, float width, const half4& color) { - layer_state_t* s = getLayerState(sc); - if (!s) { - mStatus = BAD_INDEX; - return *this; - } - - s->what |= layer_state_t::eRenderBorderChanged; - s->borderEnabled = shouldEnable; - s->borderWidth = width; - s->borderColor = color; - - registerSurfaceControlForCallback(sc); - return *this; -} - // --------------------------------------------------------------------------- DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp& token) { diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 0fedea7b9e..ca7acf9be4 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -179,7 +179,6 @@ struct layer_state_t { eCachingHintChanged = 0x00000200, eDimmingEnabledChanged = 0x00000400, eShadowRadiusChanged = 0x00000800, - eRenderBorderChanged = 0x00001000, eBufferCropChanged = 0x00002000, eRelativeLayerChanged = 0x00004000, eReparent = 0x00008000, @@ -258,8 +257,8 @@ struct layer_state_t { layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged | layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged | - layer_state_t::eHdrMetadataChanged | layer_state_t::eRenderBorderChanged | - layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged; + layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged | + layer_state_t::eStretchChanged; // Changes which invalidates the layer's visible region in CE. static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES | @@ -388,11 +387,6 @@ struct layer_state_t { // should be trusted for input occlusion detection purposes bool isTrustedOverlay; - // Flag to indicate if border needs to be enabled on the layer - bool borderEnabled; - float borderWidth; - half4 borderColor; - // Stretch effect to be applied to this layer StretchEffect stretchEffect; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 288882695a..79224e6782 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -744,9 +744,6 @@ public: const Rect& destinationFrame); Transaction& setDropInputMode(const sp& sc, gui::DropInputMode mode); - Transaction& enableBorder(const sp& sc, bool shouldEnable, float width, - const half4& color); - status_t setDisplaySurface(const sp& token, const sp& bufferProducer); diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h deleted file mode 100644 index 0ee6661f33..0000000000 --- a/libs/renderengine/include/renderengine/BorderRenderInfo.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -namespace android { -namespace renderengine { - -struct BorderRenderInfo { - float width = 0; - half4 color; - Region combinedRegion; - - bool operator==(const BorderRenderInfo& rhs) const { - return (width == rhs.width && color == rhs.color && - combinedRegion.hasSameRects(rhs.combinedRegion)); - } -}; - -} // namespace renderengine -} // namespace android \ No newline at end of file diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index 8d7c13cb18..deb62534a5 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -87,8 +86,6 @@ struct DisplaySettings { // Configures the rendering intent of the output display. This is used for tonemapping. aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC; - - std::vector borderInfoList; }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { @@ -100,8 +97,7 @@ static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform && lhs.orientation == rhs.orientation && lhs.targetLuminanceNits == rhs.targetLuminanceNits && - lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent && - lhs.borderInfoList == rhs.borderInfoList; + lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent; } static const char* orientation_to_string(uint32_t orientation) { diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 9e8fe6802d..c12c119bfd 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -1127,25 +1127,6 @@ void SkiaRenderEngine::drawLayersInternal( skgpu::ganesh::Flush(activeSurface); } } - for (const auto& borderRenderInfo : display.borderInfoList) { - SkPaint p; - p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g, - borderRenderInfo.color.b, borderRenderInfo.color.a}); - p.setAntiAlias(true); - p.setStyle(SkPaint::kStroke_Style); - p.setStrokeWidth(borderRenderInfo.width); - SkRegion sk_region; - SkPath path; - - // Construct a final SkRegion using Regions - for (const auto& r : borderRenderInfo.combinedRegion) { - sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op); - } - - sk_region.getBoundaryPath(&path); - canvas->drawPath(path, p); - path.close(); - } surfaceAutoSaveRestore.restore(); mCapture->endCapture(); diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 7b8eb8470f..3a07205ea6 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -2490,51 +2490,6 @@ TEST_P(RenderEngineTest, testDisableBlendingBuffer) { expectBufferColor(rect, 0, 128, 0, 128); } -TEST_P(RenderEngineTest, testBorder) { - if (!GetParam()->apiSupported()) { - GTEST_SKIP(); - } - - initializeRenderEngine(); - - const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB; - - const auto displayRect = Rect(1080, 2280); - renderengine::DisplaySettings display{ - .physicalDisplay = displayRect, - .clip = displayRect, - .outputDataspace = dataspace, - }; - display.borderInfoList.clear(); - renderengine::BorderRenderInfo info; - info.combinedRegion = Region(Rect(99, 99, 199, 199)); - info.width = 20.0f; - info.color = half4{1.0f, 128.0f / 255.0f, 0.0f, 1.0f}; - display.borderInfoList.emplace_back(info); - - const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255)); - const renderengine::LayerSettings greenLayer{ - .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f), - .source = - renderengine::PixelSource{ - .buffer = - renderengine::Buffer{ - .buffer = greenBuffer, - .usePremultipliedAlpha = true, - }, - }, - .alpha = 1.0f, - .sourceDataspace = dataspace, - .whitePointNits = 200.f, - }; - - std::vector layers; - layers.emplace_back(greenLayer); - invokeDraw(display, layers); - - expectBufferColor(Rect(99, 99, 101, 101), 255, 128, 0, 255, 1); -} - TEST_P(RenderEngineTest, testDimming) { if (!GetParam()->apiSupported()) { GTEST_SKIP(); diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h index 843b5c5c82..dd0f985e1c 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h @@ -34,12 +34,6 @@ namespace android::compositionengine { using Layers = std::vector>; using Outputs = std::vector>; -struct BorderRenderInfo { - float width = 0; - half4 color; - std::vector layerIds; -}; - // Interface of composition engine power hint callback. struct ICEPowerCallback { virtual void notifyCpuLoadUp() = 0; @@ -101,8 +95,6 @@ struct CompositionRefreshArgs { // TODO (b/255601557): Calculate per display. std::optional scheduledFrameTime; - std::vector borderInfoList; - bool hasTrustedPresentationListener = false; ICEPowerCallback* powerCallback = nullptr; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index 911d67b5ed..b2ef9191ba 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -162,7 +162,6 @@ protected: private: void dirtyEntireOutput(); - void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&); compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const; void finishPrepareFrame(); ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h index 6b1c318d1c..f8ffde1e51 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h @@ -34,7 +34,6 @@ #include #include -#include #include #include #include @@ -166,8 +165,6 @@ struct OutputCompositionState { bool treat170mAsSrgb = false; - std::vector borderInfoList; - uint64_t lastOutputLayerHash = 0; uint64_t outputLayerHash = 0; diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 921e05dfb2..0935a67f9b 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -818,44 +818,6 @@ void Output::updateCompositionState(const compositionengine::CompositionRefreshA forceClientComposition = false; } } - - updateCompositionStateForBorder(refreshArgs); -} - -void Output::updateCompositionStateForBorder( - const compositionengine::CompositionRefreshArgs& refreshArgs) { - std::unordered_map layerVisibleRegionMap; - // Store a map of layerId to their computed visible region. - for (auto* layer : getOutputLayersOrderedByZ()) { - int layerId = (layer->getLayerFE()).getSequence(); - layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion); - } - OutputCompositionState& outputCompositionState = editState(); - outputCompositionState.borderInfoList.clear(); - bool clientComposeTopLayer = false; - for (const auto& borderInfo : refreshArgs.borderInfoList) { - renderengine::BorderRenderInfo info; - for (const auto& id : borderInfo.layerIds) { - info.combinedRegion.orSelf(*(layerVisibleRegionMap[id])); - } - - if (!info.combinedRegion.isEmpty()) { - info.width = borderInfo.width; - info.color = borderInfo.color; - outputCompositionState.borderInfoList.emplace_back(std::move(info)); - clientComposeTopLayer = true; - } - } - - // In this situation we must client compose the top layer instead of using hwc - // because we want to draw the border above all else. - // This could potentially cause a bit of a performance regression if the top - // layer would have been rendered using hwc originally. - // TODO(b/227656283): Measure system's performance before enabling the border feature - if (clientComposeTopLayer) { - auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1); - (topLayer->editState()).forceClientComposition = true; - } } void Output::planComposition() { @@ -1443,13 +1405,6 @@ renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings( // Compute the global color transform matrix. clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix; - for (auto& info : outputState.borderInfoList) { - renderengine::BorderRenderInfo borderInfo; - borderInfo.width = info.width; - borderInfo.color = info.color; - borderInfo.combinedRegion = info.combinedRegion; - clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo)); - } clientCompositionDisplay.deviceHandlesColorTransform = outputState.usesDeviceComposition || getSkipColorTransform(); return clientCompositionDisplay; diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp index 39cf67165a..6683e67029 100644 --- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp @@ -67,11 +67,6 @@ void OutputCompositionState::dump(std::string& out) const { out.append("\n "); dumpVal(out, "treat170mAsSrgb", treat170mAsSrgb); - - out.append("\n"); - for (const auto& borderRenderInfo : borderInfoList) { - dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion); - } out.append("\n"); } diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 736fec6fb2..4606e43691 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -150,7 +150,6 @@ Layer::Layer(const surfaceflinger::LayerCreationArgs& args) mWindowType(static_cast( args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))), mLayerCreationFlags(args.flags), - mBorderEnabled(false), mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) { ALOGV("Creating Layer %s", getDebugName()); @@ -1235,28 +1234,6 @@ StretchEffect Layer::getStretchEffect() const { return StretchEffect{}; } -bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) { - if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) { - return false; - } - mBorderEnabled = shouldEnable; - mBorderWidth = width; - mBorderColor = color; - return true; -} - -bool Layer::isBorderEnabled() { - return mBorderEnabled; -} - -float Layer::getBorderWidth() { - return mBorderWidth; -} - -const half4& Layer::getBorderColor() { - return mBorderColor; -} - bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren, bool* transactionNeeded) { // Gets the frame rate to propagate to children. diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 0ceecec7ec..b5d7075574 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -874,10 +874,6 @@ public: bool setStretchEffect(const StretchEffect& effect); StretchEffect getStretchEffect() const; - bool enableBorder(bool shouldEnable, float width, const half4& color); - bool isBorderEnabled(); - float getBorderWidth(); - const half4& getBorderColor(); bool setBufferCrop(const Rect& /* bufferCrop */); bool setDestinationFrame(const Rect& /* destinationFrame */); @@ -1238,10 +1234,6 @@ private: bool findInHierarchy(const sp&); - bool mBorderEnabled = false; - float mBorderWidth; - half4 mBorderColor; - void setTransformHintLegacy(ui::Transform::RotationFlags); void releasePreviousBuffer(); void resetDrawingStateBufferInfo(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9d07671232..b1bb576586 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -167,10 +167,6 @@ #define NO_THREAD_SAFETY_ANALYSIS \ _Pragma("GCC error \"Prefer or MutexUtils.h helpers.\"") -// To enable layer borders in the system, change the below flag to true. -#undef DOES_CONTAIN_BORDER -#define DOES_CONTAIN_BORDER false - namespace android { using namespace std::chrono_literals; using namespace std::string_literals; @@ -2702,21 +2698,6 @@ CompositeResultsPerDisplay SurfaceFlinger::composite( mLayerMetadataSnapshotNeeded = false; } - if (DOES_CONTAIN_BORDER) { - refreshArgs.borderInfoList.clear(); - mDrawingState.traverse([&refreshArgs](Layer* layer) { - if (layer->isBorderEnabled()) { - compositionengine::BorderRenderInfo info; - info.width = layer->getBorderWidth(); - info.color = layer->getBorderColor(); - layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) { - info.layerIds.push_back(ilayer->getSequence()); - }); - refreshArgs.borderInfoList.emplace_back(std::move(info)); - } - }); - } - refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache); refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size()); @@ -5461,11 +5442,6 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eBlurRegionsChanged) { if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded; } - if (what & layer_state_t::eRenderBorderChanged) { - if (layer->enableBorder(s.borderEnabled, s.borderWidth, s.borderColor)) { - flags |= eTraversalNeeded; - } - } if (what & layer_state_t::eLayerStackChanged) { ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer); // We only allow setting layer stacks for top level layers, diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp index 1fd7ae06c0..988d26926d 100644 --- a/services/surfaceflinger/tests/Android.bp +++ b/services/surfaceflinger/tests/Android.bp @@ -42,7 +42,6 @@ cc_test { "EffectLayer_test.cpp", "HdrSdrRatioOverlay_test.cpp", "InvalidHandles_test.cpp", - "LayerBorder_test.cpp", "LayerCallback_test.cpp", "LayerRenderTypeTransaction_test.cpp", "LayerState_test.cpp", diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp deleted file mode 100644 index 00e134b4d2..0000000000 --- a/services/surfaceflinger/tests/LayerBorder_test.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2022 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. - */ - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -// TODO: Amend all tests when screenshots become fully reworked for borders -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wconversion" - -#include // std::chrono::seconds -#include // std::this_thread::sleep_for -#include "LayerTransactionTest.h" - -namespace android { - -class LayerBorderTest : public LayerTransactionTest { -protected: - virtual void SetUp() { - LayerTransactionTest::SetUp(); - ASSERT_EQ(NO_ERROR, mClient->initCheck()); - - toHalf3 = ColorTransformHelper::toHalf3; - toHalf4 = ColorTransformHelper::toHalf4; - - const auto ids = SurfaceComposerClient::getPhysicalDisplayIds(); - ASSERT_FALSE(ids.empty()); - const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front()); - ASSERT_FALSE(display == nullptr); - mColorOrange = toHalf4({255, 140, 0, 255}); - mParentLayer = createColorLayer("Parent layer", Color::RED); - - mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */, - 0 /* height */, PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceContainer | - ISurfaceComposerClient::eNoColorFill, - mParentLayer->getHandle()); - EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer"; - - mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */, - 0 /* height */, PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceEffect | - ISurfaceComposerClient::eNoColorFill, - mContainerLayer->getHandle()); - EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1"; - - mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */, - 0 /* height */, PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceEffect | - ISurfaceComposerClient::eNoColorFill, - mContainerLayer->getHandle()); - - EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2"; - - asTransaction([&](Transaction& t) { - t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK); - t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer); - t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque); - - t.setColor(mEffectLayer1, toHalf3(Color::BLUE)); - - t.setColor(mEffectLayer2, toHalf3(Color::GREEN)); - }); - } - - virtual void TearDown() { - // Uncomment the line right below when running any of the tests - // std::this_thread::sleep_for (std::chrono::seconds(30)); - LayerTransactionTest::TearDown(); - mParentLayer = 0; - } - - std::function toHalf3; - std::function toHalf4; - sp mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2; - half4 mColorOrange; -}; - -TEST_F(LayerBorderTest, OverlappingVisibleRegions) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - - t.enableBorder(mContainerLayer, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - - t.enableBorder(mEffectLayer1, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200)); - t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600)); - - t.enableBorder(mContainerLayer, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, EmptyVisibleRegion) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400)); - t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600)); - - t.enableBorder(mEffectLayer1, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, ZOrderAdjustment) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.setLayer(mParentLayer, 10); - t.setLayer(mEffectLayer1, 30); - t.setLayer(mEffectLayer2, 20); - - t.enableBorder(mEffectLayer1, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, GrandChildHierarchy) { - sp containerLayer2 = - mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */, - PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceContainer | - ISurfaceComposerClient::eNoColorFill, - mContainerLayer->getHandle()); - EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2"; - - sp effectLayer3 = - mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */, - PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceEffect | - ISurfaceComposerClient::eNoColorFill, - containerLayer2->getHandle()); - - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.setCrop(effectLayer3, Rect(400, 400, 800, 800)); - t.setColor(effectLayer3, toHalf3(Color::BLUE)); - - t.enableBorder(mContainerLayer, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(effectLayer3); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, TransparentAlpha) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.setAlpha(mEffectLayer1, 0.0f); - - t.enableBorder(mContainerLayer, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, SemiTransparentAlpha) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - t.setAlpha(mEffectLayer2, 0.5f); - - t.enableBorder(mEffectLayer2, true, 20, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, InvisibleLayers) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - - t.enableBorder(mContainerLayer, true, 20, mColorOrange); - t.hide(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, LayerWithBuffer) { - asTransaction([&](Transaction& t) { - t.hide(mEffectLayer1); - t.hide(mEffectLayer2); - t.show(mContainerLayer); - - sp layer = - mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */, - PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eFXSurfaceBufferState, - mContainerLayer->getHandle()); - - sp buffer = - sp::make(400u, 400u, PIXEL_FORMAT_RGBA_8888, 1u, - BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | - BufferUsage::COMPOSER_OVERLAY | - BufferUsage::GPU_TEXTURE, - "test"); - TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN); - TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE); - - t.setBuffer(layer, buffer); - t.setPosition(layer, 100, 100); - t.show(layer); - t.enableBorder(mContainerLayer, true, 20, mColorOrange); - }); -} - -TEST_F(LayerBorderTest, CustomWidth) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - - t.enableBorder(mContainerLayer, true, 50, mColorOrange); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, CustomColor) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400)); - t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600)); - - t.enableBorder(mContainerLayer, true, 20, toHalf4({255, 0, 255, 255})); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -TEST_F(LayerBorderTest, CustomWidthAndColorAndOpacity) { - asTransaction([&](Transaction& t) { - t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200)); - t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600)); - - t.enableBorder(mContainerLayer, true, 40, toHalf4({255, 255, 0, 128})); - t.show(mEffectLayer1); - t.show(mEffectLayer2); - t.show(mContainerLayer); - }); -} - -} // namespace android - -// TODO(b/129481165): remove the #pragma below and fix conversion issues -#pragma clang diagnostic pop // ignored "-Wconversion" diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h index 38c422a26d..07916b60a7 100644 --- a/services/surfaceflinger/tests/utils/ColorUtils.h +++ b/services/surfaceflinger/tests/utils/ColorUtils.h @@ -33,10 +33,6 @@ struct Color { static const Color WHITE; static const Color BLACK; static const Color TRANSPARENT; - - half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; } - - half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; } }; const Color Color::RED{255, 0, 0, 255}; @@ -85,14 +81,6 @@ public: } color = ret; } - - static half3 toHalf3(const Color& color) { - return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f}; - } - - static half4 toHalf4(const Color& color) { - return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f}; - } }; } // namespace } // namespace android -- GitLab From fe15a35ccc03bfb3049c4056db445bf6f0852581 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 19 Mar 2024 10:49:17 -0700 Subject: [PATCH 120/465] SF: dont shift vsync timeline unless missed a frame Bug: 329036771 Test: presubmit Test: com.android.uibench.microbenchmark.UiBenchInflatingHanListViewFlingMicrobenchmark Change-Id: Id0acfd4aff9b002df09fe79f175e07814f46c5a8 --- .../Scheduler/VSyncPredictor.cpp | 43 ++++++++----------- .../tests/unittests/SchedulerTest.cpp | 4 +- .../tests/unittests/VSyncPredictorTest.cpp | 21 +++++---- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index acb37603c1..b3c1f6b513 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -635,33 +635,30 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime const auto threshold = model.slope / 2; const auto lastFrameMissed = lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; - nsecs_t vsyncFixupTime = 0; if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { // If the last frame missed is the last vsync, we already shifted the timeline. Depends on // whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a different // fixup. There is no need to to shift the vsync timeline again. vsyncTime += missedVsync.fixup.ns(); ATRACE_FORMAT_INSTANT("lastFrameMissed"); - } else if (minFramePeriodOpt) { - if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) { - // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it - // first before trying to use it. - if (mLastVsyncSequence->seq > 0) { - lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); - } - const auto vsyncDiff = vsyncTime - *lastVsyncOpt; - if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) { - vsyncFixupTime = *lastVsyncOpt + minFramePeriodOpt->ns() - vsyncTime; - ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f " - "from " - "prev. " - "adjust by %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, - static_cast(vsyncTime - *lastVsyncOpt) / 1e6f, - static_cast(vsyncFixupTime) / 1e6f); - } + } else if (FlagManager::getInstance().vrr_config() && minFramePeriodOpt && mRenderRateOpt && + lastVsyncOpt) { + // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it + // first before trying to use it. + lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + const auto vsyncDiff = vsyncTime - *lastVsyncOpt; + if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) { + // avoid a duplicate vsync + ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f which " + "is %.2f " + "from " + "prev. " + "adjust by %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, + static_cast(vsyncDiff) / 1e6f, + static_cast(mRenderRateOpt->getPeriodNsecs()) / 1e6f); + vsyncTime += mRenderRateOpt->getPeriodNsecs(); } - vsyncTime += vsyncFixupTime; } ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f); @@ -671,12 +668,6 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime return std::nullopt; } - // If we needed a fixup, it means that we changed the render rate and the chosen vsync would - // cross minFramePeriod. In that case we need to shift the entire vsync timeline. - if (vsyncFixupTime > 0) { - shiftVsyncSequence(Duration::fromNs(vsyncFixupTime)); - } - return TimePoint::fromNs(vsyncTime); } diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index d4735c7558..e6bae906d8 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -607,10 +607,10 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(4500))); + TimePoint::fromNs(5500))); EXPECT_EQ(Fps::fromPeriodNsecs(2000), scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), - TimePoint::fromNs(6500))); + TimePoint::fromNs(7500))); } TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) { diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index aac1cacf97..eafba0ad8d 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -704,14 +704,17 @@ TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) { EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); - EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); - EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); - EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); - EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000)); - EXPECT_EQ(11000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); - EXPECT_EQ(13000, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000)); - EXPECT_EQ(17000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); - EXPECT_EQ(20000, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); + EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000)); + EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000)); + EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000)); + EXPECT_EQ(9500, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000)); + EXPECT_EQ(11500, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000)); + EXPECT_EQ(13500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000)); + EXPECT_EQ(16500, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500)); + EXPECT_EQ(20500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000)); + + // matches the previous cadence + EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(20500, 20500)); } TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) { @@ -820,7 +823,7 @@ TEST_F(VSyncPredictorTest, returnsCorrectVsyncWhenLastIsNot) { vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false); vrrTracker.addVsyncTimestamp(0); - EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234)); + EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234)); } TEST_F(VSyncPredictorTest, adjustsVrrTimeline) { -- GitLab From 1af8b968a8cd93d2642d9a8bf84f23a8db9d016e Mon Sep 17 00:00:00 2001 From: Derek Wu Date: Wed, 20 Mar 2024 13:53:40 -0700 Subject: [PATCH 121/465] Add flag to enable jerk prediction pruning. For now this flag will simply allow all motion predictions (no-op). Test: atest libinput_tests Bug: 266747654 Change-Id: I7f8254bb4ea6702fecbb8cd8cfa568dbd06e4f37 --- libs/input/MotionPredictor.cpp | 13 ++++++++++++- libs/input/input_flags.aconfig | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp index c4e3ff6dee..e836a4c92f 100644 --- a/libs/input/MotionPredictor.cpp +++ b/libs/input/MotionPredictor.cpp @@ -22,17 +22,21 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include

+ * Earlier, we used rust thread park()/unpark() to put the thread to sleep and wake up from sleep. + * But that caused some breakages after migrating the rust system crates to 2021 edition. Since, + * the threads are created in C++, it was more reliable to rely on C++ side of the implementation + * to implement the sleep and wake functions. + *

+ * + *

* NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't * have JNI support and can't call into Java policy that we use currently. libutils provided * Thread.h also recommends against using std::thread and using the provided infrastructure that @@ -33,6 +40,16 @@ interface IInputThread { /** Finish input thread (if not running, this call does nothing) */ void finish(); + /** Wakes up the thread (if sleeping) */ + void wake(); + + /** + * Puts the thread to sleep until a future time provided. + * + * NOTE: The thread can be awaken before the provided time using {@link wake()} function. + */ + void sleepUntil(long whenNanos); + /** Callbacks from C++ to call into inputflinger rust components */ interface IInputThreadCallback { /** diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs index a544fa36ae..6df339ed67 100644 --- a/services/inputflinger/rust/input_filter.rs +++ b/services/inputflinger/rust/input_filter.rs @@ -396,14 +396,16 @@ pub mod test_callbacks { IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback}, KeyEvent::KeyEvent, }; - use std::sync::{Arc, RwLock, RwLockWriteGuard}; + use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId}; + use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard}; + use std::time::Duration; #[derive(Default)] struct TestCallbacksInner { last_modifier_state: u32, last_locked_modifier_state: u32, last_event: Option, - test_thread: Option, + test_thread: Option, } #[derive(Default, Clone)] @@ -438,13 +440,9 @@ pub mod test_callbacks { self.0.read().unwrap().last_locked_modifier_state } - pub fn is_thread_created(&self) -> bool { - self.0.read().unwrap().test_thread.is_some() - } - - pub fn is_thread_finished(&self) -> bool { + pub fn is_thread_running(&self) -> bool { if let Some(test_thread) = &self.0.read().unwrap().test_thread { - return test_thread.is_finish_called(); + return test_thread.is_running(); } false } @@ -468,41 +466,101 @@ pub mod test_callbacks { fn createInputFilterThread( &self, - _callback: &Strong, + callback: &Strong, ) -> std::result::Result, binder::Status> { - let test_thread = TestThread::new(); + let test_thread = FakeCppThread::new(callback.clone()); + test_thread.start_looper(); self.inner().test_thread = Some(test_thread.clone()); Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default())) } } #[derive(Default)] - struct TestThreadInner { - is_finish_called: bool, + struct FakeCppThreadInner { + join_handle: Option>, } - #[derive(Default, Clone)] - struct TestThread(Arc>); + #[derive(Clone)] + struct FakeCppThread { + callback: Arc>>, + inner: Arc>, + exit_flag: Arc, + } - impl Interface for TestThread {} + impl Interface for FakeCppThread {} - impl TestThread { - pub fn new() -> Self { - Default::default() + impl FakeCppThread { + pub fn new(callback: Strong) -> Self { + let thread = Self { + callback: Arc::new(RwLock::new(callback)), + inner: Arc::new(RwLock::new(FakeCppThreadInner { join_handle: None })), + exit_flag: Arc::new(AtomicBool::new(true)), + }; + thread.create_looper(); + thread } - fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> { - self.0.write().unwrap() + fn inner(&self) -> RwLockWriteGuard<'_, FakeCppThreadInner> { + self.inner.write().unwrap() + } + + fn create_looper(&self) { + let clone = self.clone(); + let join_handle = std::thread::Builder::new() + .name("fake_cpp_thread".to_string()) + .spawn(move || loop { + if !clone.exit_flag.load(Ordering::Relaxed) { + clone.loop_once(); + } + }) + .unwrap(); + self.inner().join_handle = Some(join_handle); + // Sleep until the looper thread starts + std::thread::sleep(Duration::from_millis(10)); } - pub fn is_finish_called(&self) -> bool { - self.0.read().unwrap().is_finish_called + pub fn start_looper(&self) { + self.exit_flag.store(false, Ordering::Relaxed); + } + + pub fn stop_looper(&self) { + self.exit_flag.store(true, Ordering::Relaxed); + if let Some(join_handle) = &self.inner.read().unwrap().join_handle { + join_handle.thread().unpark(); + } + } + + pub fn is_running(&self) -> bool { + !self.exit_flag.load(Ordering::Relaxed) + } + + fn loop_once(&self) { + let _ = self.callback.read().unwrap().loopOnce(); } } - impl IInputThread for TestThread { + impl IInputThread for FakeCppThread { fn finish(&self) -> binder::Result<()> { - self.inner().is_finish_called = true; + self.stop_looper(); + Result::Ok(()) + } + + fn wake(&self) -> binder::Result<()> { + if let Some(join_handle) = &self.inner.read().unwrap().join_handle { + join_handle.thread().unpark(); + } + Result::Ok(()) + } + + fn sleepUntil(&self, wake_up_time: i64) -> binder::Result<()> { + let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds(); + if wake_up_time == i64::MAX { + std::thread::park(); + } else { + let duration_now = Duration::from_nanos(now as u64); + let duration_wake_up = Duration::from_nanos(wake_up_time as u64); + std::thread::park_timeout(duration_wake_up - duration_now); + } Result::Ok(()) } } diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs index 2d503aee70..96e5681ead 100644 --- a/services/inputflinger/rust/input_filter_thread.rs +++ b/services/inputflinger/rust/input_filter_thread.rs @@ -33,8 +33,6 @@ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::I use log::{debug, error}; use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId}; use std::sync::{Arc, RwLock, RwLockWriteGuard}; -use std::time::Duration; -use std::{thread, thread::Thread}; /// Interface to receive callback from Input filter thread pub trait ThreadCallback { @@ -54,15 +52,18 @@ pub struct InputFilterThread { thread_creator: InputFilterThreadCreator, thread_callback_handler: ThreadCallbackHandler, inner: Arc>, + looper: Arc>, } struct InputFilterThreadInner { - cpp_thread: Option>, - looper: Option, next_timeout: i64, is_finishing: bool, } +struct Looper { + cpp_thread: Option>, +} + impl InputFilterThread { /// Create a new InputFilterThread instance. /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread. @@ -71,11 +72,10 @@ impl InputFilterThread { thread_creator, thread_callback_handler: ThreadCallbackHandler::new(), inner: Arc::new(RwLock::new(InputFilterThreadInner { - cpp_thread: None, - looper: None, next_timeout: i64::MAX, is_finishing: false, })), + looper: Arc::new(RwLock::new(Looper { cpp_thread: None })), } } @@ -83,12 +83,17 @@ impl InputFilterThread { /// time on the input filter thread. /// {@see ThreadCallback.notify_timeout_expired(...)} pub fn request_timeout_at_time(&self, when_nanos: i64) { - let filter_thread = &mut self.filter_thread(); - if when_nanos < filter_thread.next_timeout { - filter_thread.next_timeout = when_nanos; - if let Some(looper) = &filter_thread.looper { - looper.unpark(); + let mut need_wake = false; + { + // acquire filter lock + let filter_thread = &mut self.filter_thread(); + if when_nanos < filter_thread.next_timeout { + filter_thread.next_timeout = when_nanos; + need_wake = true; } + } // release filter lock + if need_wake { + self.wake(); } } @@ -120,29 +125,36 @@ impl InputFilterThread { fn start(&self) { debug!("InputFilterThread: start thread"); - let filter_thread = &mut self.filter_thread(); - if filter_thread.cpp_thread.is_none() { - filter_thread.cpp_thread = Some(self.thread_creator.create( - &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()), - )); - filter_thread.looper = None; - filter_thread.is_finishing = false; - } + { + // acquire looper lock + let looper = &mut self.looper(); + if looper.cpp_thread.is_none() { + looper.cpp_thread = Some(self.thread_creator.create( + &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()), + )); + } + } // release looper lock + self.set_finishing(false); } fn stop(&self) { debug!("InputFilterThread: stop thread"); + self.set_finishing(true); + self.wake(); + { + // acquire looper lock + let looper = &mut self.looper(); + if let Some(cpp_thread) = &looper.cpp_thread { + let _ = cpp_thread.finish(); + } + // Clear all references + looper.cpp_thread = None; + } // release looper lock + } + + fn set_finishing(&self, is_finishing: bool) { let filter_thread = &mut self.filter_thread(); - filter_thread.is_finishing = true; - if let Some(looper) = &filter_thread.looper { - looper.unpark(); - } - if let Some(cpp_thread) = &filter_thread.cpp_thread { - let _ = cpp_thread.finish(); - } - // Clear all references - filter_thread.cpp_thread = None; - filter_thread.looper = None; + filter_thread.is_finishing = is_finishing; } fn loop_once(&self, now: i64) { @@ -163,25 +175,34 @@ impl InputFilterThread { wake_up_time = filter_thread.next_timeout; } } - if filter_thread.looper.is_none() { - filter_thread.looper = Some(std::thread::current()); - } } // release thread lock if timeout_expired { self.thread_callback_handler.notify_timeout_expired(now); } - if wake_up_time == i64::MAX { - thread::park(); - } else { - let duration_now = Duration::from_nanos(now as u64); - let duration_wake_up = Duration::from_nanos(wake_up_time as u64); - thread::park_timeout(duration_wake_up - duration_now); - } + self.sleep_until(wake_up_time); } fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> { self.inner.write().unwrap() } + + fn sleep_until(&self, when_nanos: i64) { + let looper = self.looper.read().unwrap(); + if let Some(cpp_thread) = &looper.cpp_thread { + let _ = cpp_thread.sleepUntil(when_nanos); + } + } + + fn wake(&self) { + let looper = self.looper.read().unwrap(); + if let Some(cpp_thread) = &looper.cpp_thread { + let _ = cpp_thread.wake(); + } + } + + fn looper(&self) -> RwLockWriteGuard<'_, Looper> { + self.looper.write().unwrap() + } } impl Interface for InputFilterThread {} @@ -252,165 +273,64 @@ impl ThreadCallbackHandler { #[cfg(test)] mod tests { - use crate::input_filter::test_callbacks::TestCallbacks; - use crate::input_filter_thread::{ - test_thread::TestThread, test_thread_callback::TestThreadCallback, - }; + use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator}; + use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread}; + use binder::Strong; + use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId}; + use std::sync::{Arc, RwLock}; + use std::time::Duration; #[test] fn test_register_callback_creates_cpp_thread() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let test_thread_callback = TestThreadCallback::new(); - test_thread.register_thread_callback(test_thread_callback); - assert!(test_callbacks.is_thread_created()); + test_thread.register_thread_callback(Box::new(test_thread_callback)); + assert!(test_callbacks.is_thread_running()); } #[test] fn test_unregister_callback_finishes_cpp_thread() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let test_thread_callback = TestThreadCallback::new(); - test_thread.register_thread_callback(test_thread_callback.clone()); - test_thread.unregister_thread_callback(test_thread_callback); - assert!(test_callbacks.is_thread_finished()); + test_thread.register_thread_callback(Box::new(test_thread_callback.clone())); + test_thread.unregister_thread_callback(Box::new(test_thread_callback)); + assert!(!test_callbacks.is_thread_running()); } #[test] fn test_notify_timeout_called_after_timeout_expired() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let test_thread_callback = TestThreadCallback::new(); - test_thread.register_thread_callback(test_thread_callback.clone()); - test_thread.start_looper(); + test_thread.register_thread_callback(Box::new(test_thread_callback.clone())); - test_thread.request_timeout_at_time(500); - test_thread.dispatch_next(); + let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds(); + test_thread.request_timeout_at_time((now + 10) * 1000000); - test_thread.move_time_forward(500); - - test_thread.stop_looper(); + std::thread::sleep(Duration::from_millis(20)); assert!(test_thread_callback.is_notify_timeout_called()); } #[test] fn test_notify_timeout_not_called_before_timeout_expired() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let test_thread_callback = TestThreadCallback::new(); - test_thread.register_thread_callback(test_thread_callback.clone()); - test_thread.start_looper(); - - test_thread.request_timeout_at_time(500); - test_thread.dispatch_next(); + test_thread.register_thread_callback(Box::new(test_thread_callback.clone())); - test_thread.move_time_forward(100); + let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds(); + test_thread.request_timeout_at_time((now + 100) * 1000000); - test_thread.stop_looper(); + std::thread::sleep(Duration::from_millis(10)); assert!(!test_thread_callback.is_notify_timeout_called()); } -} - -#[cfg(test)] -pub mod test_thread { - - use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator}; - use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread}; - use binder::Strong; - use std::sync::{ - atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard, - }; - use std::time::Duration; - - #[derive(Clone)] - pub struct TestThread { - input_thread: InputFilterThread, - inner: Arc>, - exit_flag: Arc, - now: Arc, - } - - struct TestThreadInner { - join_handle: Option>, - } - impl TestThread { - pub fn new(callbacks: TestCallbacks) -> TestThread { - Self { - input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new( - RwLock::new(Strong::new(Box::new(callbacks))), - ))), - inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })), - exit_flag: Arc::new(AtomicBool::new(false)), - now: Arc::new(AtomicI64::new(0)), - } - } - - fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> { - self.inner.write().unwrap() - } - - pub fn get_input_thread(&self) -> InputFilterThread { - self.input_thread.clone() - } - - pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) { - self.input_thread.register_thread_callback(Box::new(thread_callback)); - } - - pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) { - self.input_thread.unregister_thread_callback(Box::new(thread_callback)); - } - - pub fn start_looper(&self) { - self.exit_flag.store(false, Ordering::Relaxed); - let clone = self.clone(); - let join_handle = std::thread::Builder::new() - .name("test_thread".to_string()) - .spawn(move || { - while !clone.exit_flag.load(Ordering::Relaxed) { - clone.loop_once(); - } - }) - .unwrap(); - self.inner().join_handle = Some(join_handle); - // Sleep until the looper thread starts - std::thread::sleep(Duration::from_millis(10)); - } - - pub fn stop_looper(&self) { - self.exit_flag.store(true, Ordering::Relaxed); - { - let mut inner = self.inner(); - if let Some(join_handle) = &inner.join_handle { - join_handle.thread().unpark(); - } - inner.join_handle.take().map(std::thread::JoinHandle::join); - inner.join_handle = None; - } - self.exit_flag.store(false, Ordering::Relaxed); - } - - pub fn move_time_forward(&self, value: i64) { - let _ = self.now.fetch_add(value, Ordering::Relaxed); - self.dispatch_next(); - } - - pub fn dispatch_next(&self) { - if let Some(join_handle) = &self.inner().join_handle { - join_handle.thread().unpark(); - } - // Sleep until the looper thread runs a loop - std::thread::sleep(Duration::from_millis(10)); - } - - fn loop_once(&self) { - self.input_thread.loop_once(self.now.load(Ordering::Relaxed)); - } - - pub fn request_timeout_at_time(&self, when_nanos: i64) { - self.input_thread.request_timeout_at_time(when_nanos); - } + fn get_thread(callbacks: TestCallbacks) -> InputFilterThread { + InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new( + Box::new(callbacks), + ))))) } } diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs index 09fbf40842..0f18a2f395 100644 --- a/services/inputflinger/rust/slow_keys_filter.rs +++ b/services/inputflinger/rust/slow_keys_filter.rs @@ -207,13 +207,19 @@ impl ThreadCallback for SlowKeysFilter { #[cfg(test)] mod tests { - use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter}; - use crate::input_filter_thread::test_thread::TestThread; + use crate::input_filter::{ + test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, InputFilterThreadCreator, + }; + use crate::input_filter_thread::InputFilterThread; use crate::slow_keys_filter::{SlowKeysFilter, POLICY_FLAG_DISABLE_KEY_REPEAT}; use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; + use binder::Strong; use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, }; + use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId}; + use std::sync::{Arc, RwLock}; + use std::time::Duration; static BASE_KEY_EVENT: KeyEvent = KeyEvent { id: 1, @@ -231,18 +237,19 @@ mod tests { metaState: 0, }; + static SLOW_KEYS_THRESHOLD_NS: i64 = 100 * 1000000; // 100 ms + #[test] fn test_is_notify_key_for_internal_keyboard_not_blocked() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let next = TestFilter::new(); let mut filter = setup_filter_with_internal_device( Box::new(next.clone()), test_thread.clone(), - 1, /* device_id */ - 100, /* threshold */ + 1, /* device_id */ + SLOW_KEYS_THRESHOLD_NS, ); - test_thread.start_looper(); let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }; filter.notify_key(&event); @@ -252,15 +259,14 @@ mod tests { #[test] fn test_is_notify_key_for_external_stylus_not_blocked() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let next = TestFilter::new(); let mut filter = setup_filter_with_external_device( Box::new(next.clone()), test_thread.clone(), - 1, /* device_id */ - 100, /* threshold */ + 1, /* device_id */ + SLOW_KEYS_THRESHOLD_NS, ); - test_thread.start_looper(); let event = KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT }; @@ -271,89 +277,115 @@ mod tests { #[test] fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let next = TestFilter::new(); let mut filter = setup_filter_with_external_device( Box::new(next.clone()), test_thread.clone(), - 1, /* device_id */ - 100, /* threshold */ + 1, /* device_id */ + SLOW_KEYS_THRESHOLD_NS, ); - test_thread.start_looper(); - - filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }); + let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds(); + filter.notify_key(&KeyEvent { + action: KeyEventAction::DOWN, + downTime: down_time, + eventTime: down_time, + ..BASE_KEY_EVENT + }); assert!(next.last_event().is_none()); - test_thread.dispatch_next(); - test_thread.move_time_forward(100); - - test_thread.stop_looper(); + std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64)); assert_eq!( next.last_event().unwrap(), KeyEvent { action: KeyEventAction::DOWN, - downTime: 100, - eventTime: 100, + downTime: down_time + SLOW_KEYS_THRESHOLD_NS, + eventTime: down_time + SLOW_KEYS_THRESHOLD_NS, policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT, ..BASE_KEY_EVENT } ); + + let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds(); + filter.notify_key(&KeyEvent { + action: KeyEventAction::UP, + downTime: down_time, + eventTime: up_time, + ..BASE_KEY_EVENT + }); + + assert_eq!( + next.last_event().unwrap(), + KeyEvent { + action: KeyEventAction::UP, + downTime: down_time + SLOW_KEYS_THRESHOLD_NS, + eventTime: up_time, + ..BASE_KEY_EVENT + } + ); } #[test] fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let next = TestFilter::new(); let mut filter = setup_filter_with_external_device( Box::new(next.clone()), test_thread.clone(), - 1, /* device_id */ - 100, /* threshold */ + 1, /* device_id */ + SLOW_KEYS_THRESHOLD_NS, ); - test_thread.start_looper(); - - filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }); - test_thread.dispatch_next(); - - test_thread.move_time_forward(10); - - filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT }); - test_thread.dispatch_next(); + let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds(); + filter.notify_key(&KeyEvent { + action: KeyEventAction::DOWN, + downTime: now, + eventTime: now, + ..BASE_KEY_EVENT + }); + + std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2)); + + now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds(); + filter.notify_key(&KeyEvent { + action: KeyEventAction::UP, + downTime: now, + eventTime: now, + ..BASE_KEY_EVENT + }); - test_thread.stop_looper(); assert!(next.last_event().is_none()); } #[test] fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() { let test_callbacks = TestCallbacks::new(); - let test_thread = TestThread::new(test_callbacks.clone()); + let test_thread = get_thread(test_callbacks.clone()); let next = TestFilter::new(); let mut filter = setup_filter_with_external_device( Box::new(next.clone()), test_thread.clone(), - 1, /* device_id */ - 100, /* threshold */ + 1, /* device_id */ + SLOW_KEYS_THRESHOLD_NS, ); - test_thread.start_looper(); - filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT }); - assert!(next.last_event().is_none()); - test_thread.dispatch_next(); + let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds(); + filter.notify_key(&KeyEvent { + action: KeyEventAction::DOWN, + downTime: now, + eventTime: now, + ..BASE_KEY_EVENT + }); filter.notify_devices_changed(&[]); - test_thread.dispatch_next(); - - test_thread.move_time_forward(100); + std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64)); - test_thread.stop_looper(); assert!(next.last_event().is_none()); } fn setup_filter_with_external_device( next: Box, - test_thread: TestThread, + test_thread: InputFilterThread, device_id: i32, threshold: i64, ) -> SlowKeysFilter { @@ -367,7 +399,7 @@ mod tests { fn setup_filter_with_internal_device( next: Box, - test_thread: TestThread, + test_thread: InputFilterThread, device_id: i32, threshold: i64, ) -> SlowKeysFilter { @@ -381,12 +413,18 @@ mod tests { fn setup_filter_with_devices( next: Box, - test_thread: TestThread, + test_thread: InputFilterThread, devices: &[DeviceInfo], threshold: i64, ) -> SlowKeysFilter { - let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread()); + let mut filter = SlowKeysFilter::new(next, threshold, test_thread); filter.notify_devices_changed(devices); filter } + + fn get_thread(callbacks: TestCallbacks) -> InputFilterThread { + InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new( + Box::new(callbacks), + ))))) + } } -- GitLab From b00612688d861942b431e8d3132c70f99bc63f11 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 19 Mar 2024 18:20:14 +0000 Subject: [PATCH 192/465] InputDevice: switch Sony DualShock 4 to new touchpad stack Bug: <329585708> Test: use the DS4's touchpad over USB and Bluetooth, check it works smoothly (or, as smoothly as it did with the old stack) Test: atest inputflinger_tests (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:7577a066e32bbf87bbecc574ce7de75f1848ea91) Merged-In: Ie57b7112bcaa2306b2b5171707822c46bc9f2c52 Change-Id: Ie57b7112bcaa2306b2b5171707822c46bc9f2c52 24D1-dev is based on 24Q2-release. Therefore, we merged this CL to 24D1-dev. --- services/inputflinger/reader/InputDevice.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 2baf576903..4d8ffb68bb 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -509,13 +509,8 @@ std::vector> InputDevice::createMappers( // Touchscreens and touchpad devices. static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY = sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true); - // TODO(b/272518665): Fix the new touchpad stack for Sony DualShock 4 (5c4, 9cc) touchpads, or - // at least load this setting from the IDC file. - const InputDeviceIdentifier identifier = contextPtr.getDeviceIdentifier(); - const bool isSonyDualShock4Touchpad = identifier.vendor == 0x054c && - (identifier.product == 0x05c4 || identifier.product == 0x09cc); if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) && - classes.test(InputDeviceClass::TOUCH_MT) && !isSonyDualShock4Touchpad) { + classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(createInputMapper(contextPtr, readerConfig)); } else if (classes.test(InputDeviceClass::TOUCH_MT)) { mappers.push_back(createInputMapper(contextPtr, readerConfig)); -- GitLab From 2c688c5163555df27f691b6076fa601a908a3302 Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Thu, 28 Mar 2024 20:12:46 +0000 Subject: [PATCH 193/465] Skip HIDL tests in libbinderthreadstate tests when HIDL isn't supported We can get/register the HIDL service if HIDL isn't supported on the device. Test: atest libbinderthreadstateutils_test Bug: 218588089 Change-Id: Ibfe89f6e029af3acccb93278e23243b0b81b2cd3 --- libs/binderthreadstate/test.cpp | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/libs/binderthreadstate/test.cpp b/libs/binderthreadstate/test.cpp index b5c4010c7a..e888b0aea8 100644 --- a/libs/binderthreadstate/test.cpp +++ b/libs/binderthreadstate/test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ using android::OK; using android::sp; using android::String16; using android::binder::Status; +using android::hardware::isHidlSupported; using android::hardware::Return; using binderthreadstateutilstest::V1_0::IHidlStuff; @@ -67,6 +69,7 @@ std::string id2name(size_t id) { // complicated calls are possible, but this should do here. static void callHidl(size_t id, int32_t idx) { + CHECK_EQ(true, isHidlSupported()) << "We shouldn't be calling HIDL if it's not supported"; auto stuff = IHidlStuff::getService(id2name(id)); CHECK(stuff->call(idx).isOk()); } @@ -174,6 +177,7 @@ TEST(BinderThreadState, DoesntInitializeBinderDriver) { } TEST(BindThreadState, RemoteHidlCall) { + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; auto stuff = IHidlStuff::getService(id2name(kP1Id)); ASSERT_NE(nullptr, stuff); ASSERT_TRUE(stuff->call(0).isOk()); @@ -186,11 +190,14 @@ TEST(BindThreadState, RemoteAidlCall) { } TEST(BindThreadState, RemoteNestedStartHidlCall) { + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; auto stuff = IHidlStuff::getService(id2name(kP1Id)); ASSERT_NE(nullptr, stuff); ASSERT_TRUE(stuff->call(100).isOk()); } TEST(BindThreadState, RemoteNestedStartAidlCall) { + // this test case is trying ot nest a HIDL call which requires HIDL support + if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device"; sp stuff; ASSERT_EQ(OK, android::getService(String16(id2name(kP1Id).c_str()), &stuff)); ASSERT_NE(nullptr, stuff); @@ -205,11 +212,15 @@ int server(size_t thisId, size_t otherId) { defaultServiceManager()->addService(String16(id2name(thisId).c_str()), aidlServer)); android::ProcessState::self()->startThreadPool(); - // HIDL - android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/); - sp hidlServer = new HidlServer(thisId, otherId); - CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str())); - android::hardware::joinRpcThreadpool(); + if (isHidlSupported()) { + // HIDL + android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/); + sp hidlServer = new HidlServer(thisId, otherId); + CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str())); + android::hardware::joinRpcThreadpool(); + } else { + android::IPCThreadState::self()->joinThreadPool(true); + } return EXIT_FAILURE; } @@ -227,9 +238,15 @@ int main(int argc, char** argv) { } android::waitForService(String16(id2name(kP1Id).c_str())); - android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP1Id).c_str()); + if (isHidlSupported()) { + android::hardware::details::waitForHwService(IHidlStuff::descriptor, + id2name(kP1Id).c_str()); + } android::waitForService(String16(id2name(kP2Id).c_str())); - android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP2Id).c_str()); + if (isHidlSupported()) { + android::hardware::details::waitForHwService(IHidlStuff::descriptor, + id2name(kP2Id).c_str()); + } return RUN_ALL_TESTS(); } -- GitLab From 7c88bdd914188e8948da9f44a8f1d7b7ef770159 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Wed, 3 Apr 2024 21:10:38 +0000 Subject: [PATCH 194/465] Re-enable 3 output dataspace RE tests on Graphite These were all fixed with Skia change I455b95ad3150a28ec92c9a9d236890f15782ba3d ("[graphite] Implement RuntimeEffect toLinearSrgb/fromLinearSrgb handling") Bug: b/331445583 Bug: b/331447131 Bug: b/331446495 Change-Id: If5d9a119ec18eb4e744543e37b89f3c69489cdd8 Test: librenderengine_test --- libs/renderengine/tests/RenderEngineTest.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 4bd0852b05..64feb80718 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -1687,11 +1687,6 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_sourceDataspace) { } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) { - // TODO: b/331445583 - Fix in Graphite and re-enable. - if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) { - GTEST_SKIP(); - } - const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->apiSupported()) { @@ -1842,11 +1837,6 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_o } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_opaqueBufferSource) { - // TODO: b/331447131 - Fix in Graphite and re-enable. - if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) { - GTEST_SKIP(); - } - const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->apiSupported()) { @@ -1997,11 +1987,6 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndSourceDataspace_b } TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_bufferSource) { - // TODO: b/331446495 - Fix in Graphite and re-enable. - if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) { - GTEST_SKIP(); - } - const auto& renderEngineFactory = GetParam(); // skip for non color management if (!renderEngineFactory->apiSupported()) { -- GitLab From 44c0a8f8396784080773acf8e5be9f3196d7c108 Mon Sep 17 00:00:00 2001 From: Wiwit Rifa'i Date: Tue, 2 Apr 2024 18:01:22 +0800 Subject: [PATCH 195/465] Exclude source crop from geometry hash calculation Bug: 305718400 Test: video playback; libcompositionengine_test Change-Id: I583f47e68c073e1d4cfdfa08f7184fb7117c72fc --- .../src/planner/LayerState.cpp | 5 +++++ .../tests/planner/LayerStateTest.cpp | 5 +++++ .../tests/planner/PredictorTest.cpp | 21 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp index 0e3fdbb0dc..10dc9276d2 100644 --- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp +++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include namespace { @@ -70,6 +71,10 @@ size_t LayerState::getHash() const { if (field->getField() == LayerStateField::Buffer) { continue; } + if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() && + field->getField() == LayerStateField::SourceCrop) { + continue; + } android::hashCombineSingleHashed(hash, field->getHash()); } diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp index 044917ead9..954eb27181 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "LayerStateTest" #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include "android/hardware_buffer.h" +#include "com_android_graphics_surfaceflinger_flags.h" #include "compositionengine/LayerFECompositionState.h" #include @@ -454,6 +456,9 @@ TEST_F(LayerStateTest, updateSourceCrop) { } TEST_F(LayerStateTest, compareSourceCrop) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); OutputLayerCompositionState outputLayerCompositionState; outputLayerCompositionState.sourceCrop = sFloatRectOne; LayerFECompositionState layerFECompositionState; diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp index 35d0ffb6e9..a1210b4e21 100644 --- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp @@ -18,6 +18,9 @@ #undef LOG_TAG #define LOG_TAG "PredictorTest" +#include +#include "com_android_graphics_surfaceflinger_flags.h" + #include #include #include @@ -127,6 +130,9 @@ TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchDifferentCompositionTypes } TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -158,6 +164,9 @@ TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) } TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -304,6 +313,9 @@ TEST_F(LayerStackTest, getApproximateMatch_alwaysMatchesClientComposition) { } TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -347,6 +359,9 @@ struct PredictionTest : public testing::Test { }; TEST_F(LayerStackTest, reorderingChangesNonBufferHash) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -467,6 +482,9 @@ TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveExactMatch) { } TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ @@ -504,6 +522,9 @@ TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatc } TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags:: + cache_when_source_crop_layer_only_moved, + false); mock::OutputLayer outputLayerOne; sp layerFEOne = sp::make(); OutputLayerCompositionState outputLayerCompositionStateOne{ -- GitLab From 9a9897d154f9230325113d18c8d6f4cee7ca72d7 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 21 Mar 2024 21:52:57 +0000 Subject: [PATCH 196/465] InputTracer: Add tests for perfetto backend Add a new InputTraceSession class that encapsulates the logic to take and decode a perfetto input trace for testing. Then, use it to add new tests to verify the behavior of entire tracing framework, using InputDispatcher as the interface. Bug: 210460522 Test: atest inputflinger_tests Change-Id: I5a9b47e831c5c30e5d7f2b60c9b8075b7d330e9e --- .../inputflinger/InputCommonConverter.cpp | 30 + services/inputflinger/InputCommonConverter.h | 17 +- .../trace/InputTracingPerfettoBackend.cpp | 14 +- .../trace/InputTracingPerfettoBackend.h | 3 + .../dispatcher/trace/ThreadedBackend.cpp | 35 + .../dispatcher/trace/ThreadedBackend.h | 12 + .../inputflinger/include/NotifyArgsBuilders.h | 29 +- services/inputflinger/tests/Android.bp | 2 + .../tests/FakeInputDispatcherPolicy.cpp | 11 +- .../tests/FakeInputDispatcherPolicy.h | 3 + services/inputflinger/tests/FakeWindows.h | 20 +- .../inputflinger/tests/InputTraceSession.cpp | 209 +++++ .../inputflinger/tests/InputTraceSession.h | 85 ++ .../inputflinger/tests/InputTracingTest.cpp | 732 ++++++++++++++++++ 14 files changed, 1179 insertions(+), 23 deletions(-) create mode 100644 services/inputflinger/tests/InputTraceSession.cpp create mode 100644 services/inputflinger/tests/InputTraceSession.h create mode 100644 services/inputflinger/tests/InputTracingTest.cpp diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 6ccd9e7697..417c1f333b 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -20,6 +20,9 @@ using namespace ::aidl::android::hardware::input; namespace android { +const static ui::Transform kIdentityTransform; +const static std::array kInvalidHmac{}; + static common::Source getSource(uint32_t source) { static_assert(static_cast(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN, "SOURCE_UNKNOWN mismatch"); @@ -337,4 +340,31 @@ common::MotionEvent notifyMotionArgsToHalMotionEvent(const NotifyMotionArgs& arg return event; } +MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform, + const ui::Transform* rawTransform, const std::array* hmac) { + if (transform == nullptr) transform = &kIdentityTransform; + if (rawTransform == nullptr) rawTransform = &kIdentityTransform; + if (hmac == nullptr) hmac = &kInvalidHmac; + + MotionEvent event; + event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action, + args.actionButton, args.flags, args.edgeFlags, args.metaState, + args.buttonState, args.classification, *transform, args.xPrecision, + args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform, + args.downTime, args.eventTime, args.getPointerCount(), + args.pointerProperties.data(), args.pointerCoords.data()); + return event; +} + +KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount, + const std::array* hmac) { + if (hmac == nullptr) hmac = &kInvalidHmac; + + KeyEvent event; + event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action, + args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount, + args.downTime, args.eventTime); + return event; +} + } // namespace android diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h index 4d3b76885f..0d4cbb0c96 100644 --- a/services/inputflinger/InputCommonConverter.h +++ b/services/inputflinger/InputCommonConverter.h @@ -16,16 +16,25 @@ #pragma once +#include "InputListener.h" + #include #include -#include "InputListener.h" +#include namespace android { -/** - * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent - */ +/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */ ::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent( const NotifyMotionArgs& args); +/** Convert from NotifyMotionArgs to MotionEvent. */ +MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr, + const ui::Transform* rawTransform = nullptr, + const std::array* hmac = nullptr); + +/** Convert from NotifyKeyArgs to KeyEvent. */ +KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0, + const std::array* hmac = nullptr); + } // namespace android diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 9c39743569..91ebe9bb5f 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -149,6 +149,8 @@ bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, // --- PerfettoBackend --- +bool PerfettoBackend::sUseInProcessBackendForTest{false}; + std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; std::atomic PerfettoBackend::sNextInstanceId{1}; @@ -159,7 +161,8 @@ PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid) // we never unregister the InputEventDataSource. std::call_once(sDataSourceRegistrationFlag, []() { perfetto::TracingInitArgs args; - args.backends = perfetto::kSystemBackend; + args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend + : perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); // Register our custom data source for input event tracing. @@ -175,6 +178,9 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource.valid()) { + return; + } dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; @@ -196,6 +202,9 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource.valid()) { + return; + } dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; @@ -217,6 +226,9 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource.valid()) { + return; + } dataSource->initializeUidMap(mGetPackageUid); if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) { return; diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index e945066dff..fdfe495c45 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -51,6 +51,8 @@ class PerfettoBackend : public InputTracingBackendInterface { public: using GetPackageUid = std::function; + static bool sUseInProcessBackendForTest; + explicit PerfettoBackend(GetPackageUid); ~PerfettoBackend() override = default; @@ -61,6 +63,7 @@ public: private: // Implementation of the perfetto data source. // Each instance of the InputEventDataSource represents a different tracing session. + // Its lifecycle is controlled by perfetto. class InputEventDataSource : public perfetto::DataSource { public: explicit InputEventDataSource(); diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp index 77d09cbb4b..c0a98f5e5d 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp @@ -84,13 +84,18 @@ void ThreadedBackend::threadLoop() { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); + setIdleStatus(true); + // Wait until we need to process more events or exit. mThreadWakeCondition.wait(lock, [&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); }); if (mThreadExit) { + setIdleStatus(true); return; } + setIdleStatus(false); + mQueue.swap(entries); } // release lock @@ -109,6 +114,36 @@ void ThreadedBackend::threadLoop() { entries.clear(); } +template +std::function ThreadedBackend::getIdleWaiterForTesting() { + std::scoped_lock lock(mLock); + if (!mIdleWaiter) { + mIdleWaiter = std::make_shared(); + } + + // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend + // beyond this threaded backend object. + return [idleWaiter = mIdleWaiter]() { + std::unique_lock idleLock(idleWaiter->idleLock); + base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock); + idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) { + return idleWaiter->isIdle; + }); + }; +} + +template +void ThreadedBackend::setIdleStatus(bool isIdle) { + if (!mIdleWaiter) { + return; + } + std::scoped_lock idleLock(mIdleWaiter->idleLock); + mIdleWaiter->isIdle = isIdle; + if (isIdle) { + mIdleWaiter->threadIdleCondition.notify_all(); + } +} + // Explicit template instantiation for the PerfettoBackend. template class ThreadedBackend; diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h index 650a87e452..52a84c470c 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h @@ -42,6 +42,9 @@ public: void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override; void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override; + /** Returns a function that, when called, will block until the tracing thread is idle. */ + std::function getIdleWaiterForTesting(); + private: std::mutex mLock; bool mThreadExit GUARDED_BY(mLock){false}; @@ -52,12 +55,21 @@ private: TracedEventMetadata>; std::vector mQueue GUARDED_BY(mLock); + struct IdleWaiter { + std::mutex idleLock; + std::condition_variable threadIdleCondition; + bool isIdle GUARDED_BY(idleLock){false}; + }; + // The lazy-initialized object used to wait for the tracing thread to idle. + std::shared_ptr mIdleWaiter GUARDED_BY(mLock); + // InputThread stops when its destructor is called. Initialize it last so that it is the // first thing to be destructed. This will guarantee the thread will not access other // members that have already been destructed. InputThread mTracerThread; void threadLoop(); + void setIdleStatus(bool isIdle) REQUIRES(mLock); }; } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h index 8ffbc11a13..1bd55958d9 100644 --- a/services/inputflinger/include/NotifyArgsBuilders.h +++ b/services/inputflinger/include/NotifyArgsBuilders.h @@ -30,8 +30,11 @@ namespace android { class MotionArgsBuilder { public: - MotionArgsBuilder(int32_t action, int32_t source) { + MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) { mAction = action; + if (mAction == AMOTION_EVENT_ACTION_CANCEL) { + addFlag(AMOTION_EVENT_FLAG_CANCELED); + } mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); mDownTime = mEventTime; @@ -97,7 +100,7 @@ public: return *this; } - NotifyMotionArgs build() { + NotifyMotionArgs build() const { std::vector pointerProperties; std::vector pointerCoords; for (const PointerBuilder& pointer : mPointers) { @@ -106,19 +109,17 @@ public: } // Set mouse cursor position for the most common cases to avoid boilerplate. + float resolvedCursorX = mRawXCursorPosition; + float resolvedCursorY = mRawYCursorPosition; if (mSource == AINPUT_SOURCE_MOUSE && !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) && BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) { - mRawXCursorPosition = pointerCoords[0].getX(); - mRawYCursorPosition = pointerCoords[0].getY(); - } - - if (mAction == AMOTION_EVENT_ACTION_CANCEL) { - addFlag(AMOTION_EVENT_FLAG_CANCELED); + resolvedCursorX = pointerCoords[0].getX(); + resolvedCursorY = pointerCoords[0].getY(); } - return {InputEvent::nextId(), + return {mEventId, mEventTime, /*readTime=*/mEventTime, mDeviceId, @@ -137,13 +138,14 @@ public: pointerCoords.data(), /*xPrecision=*/0, /*yPrecision=*/0, - mRawXCursorPosition, - mRawYCursorPosition, + resolvedCursorX, + resolvedCursorY, mDownTime, /*videoFrames=*/{}}; } private: + const int32_t mEventId; int32_t mAction; int32_t mDeviceId{DEFAULT_DEVICE_ID}; uint32_t mSource; @@ -163,7 +165,7 @@ private: class KeyArgsBuilder { public: - KeyArgsBuilder(int32_t action, int32_t source) { + KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) { mAction = action; mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); @@ -206,7 +208,7 @@ public: } NotifyKeyArgs build() const { - return {InputEvent::nextId(), + return {mEventId, mEventTime, /*readTime=*/mEventTime, mDeviceId, @@ -222,6 +224,7 @@ public: } private: + const int32_t mEventId; int32_t mAction; int32_t mDeviceId = DEFAULT_DEVICE_ID; uint32_t mSource; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 6ae9790aac..9b5db23151 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -67,6 +67,8 @@ cc_test { "InputProcessorConverter_test.cpp", "InputDispatcher_test.cpp", "InputReader_test.cpp", + "InputTraceSession.cpp", + "InputTracingTest.cpp", "InstrumentedInputReader.cpp", "LatencyTracker_test.cpp", "MultiTouchMotionAccumulator_test.cpp", diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp index e231bccb79..1360cd0208 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp @@ -466,8 +466,15 @@ void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal( mFilteredEvent = nullptr; } -gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string) { - return gui::Uid::INVALID; +gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string pkg) { + std::scoped_lock lock(mLock); + auto it = mPackageUidMap.find(pkg); + return it != mPackageUidMap.end() ? it->second : gui::Uid::INVALID; +} + +void FakeInputDispatcherPolicy::addPackageUidMapping(std::string package, gui::Uid uid) { + std::scoped_lock lock(mLock); + mPackageUidMap.insert_or_assign(std::move(package), uid); } } // namespace android diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index d83924f202..2cc018ef05 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -115,6 +115,7 @@ public: void setUnhandledKeyHandler(std::function(const KeyEvent&)> handler); void assertUnhandledKeyReported(int32_t keycode); void assertUnhandledKeyNotReported(); + void addPackageUidMapping(std::string package, gui::Uid uid); private: std::mutex mLock; @@ -150,6 +151,8 @@ private: std::queue mReportedUnhandledKeycodes GUARDED_BY(mLock); std::function(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock); + std::map mPackageUidMap GUARDED_BY(mLock); + /** * All three ANR-related callbacks behave the same way, so we use this generic function to wait * for a specific container to become non-empty. When the container is non-empty, return the diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index c0c8975e76..26c2b4b1e7 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -157,6 +157,16 @@ public: inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); } + inline void setSecure(bool secure) { + if (secure) { + mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE; + } else { + using namespace ftl::flag_operators; + mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE; + } + mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_TRACING, secure); + } + inline void setInterceptsStylus(bool interceptsStylus) { mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus); } @@ -229,10 +239,14 @@ public: std::unique_ptr consumeKey(bool handled = true); - inline void consumeKeyEvent(const ::testing::Matcher& matcher) { + inline std::unique_ptr consumeKeyEvent(const ::testing::Matcher& matcher) { std::unique_ptr keyEvent = consumeKey(); - ASSERT_NE(nullptr, keyEvent); - ASSERT_THAT(*keyEvent, matcher); + EXPECT_NE(nullptr, keyEvent); + if (!keyEvent) { + return nullptr; + } + EXPECT_THAT(*keyEvent, matcher); + return keyEvent; } inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp new file mode 100644 index 0000000000..32acb5f288 --- /dev/null +++ b/services/inputflinger/tests/InputTraceSession.cpp @@ -0,0 +1,209 @@ +/* + * Copyright 2024 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. + */ + +#include "InputTraceSession.h" + +#include +#include +#include +#include + +#include + +namespace android { + +using perfetto::protos::pbzero::AndroidInputEvent; +using perfetto::protos::pbzero::AndroidInputEventConfig; +using perfetto::protos::pbzero::AndroidKeyEvent; +using perfetto::protos::pbzero::AndroidMotionEvent; +using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent; + +// These operator<< definitions must be in the global namespace for them to be accessible to the +// GTEST library. They cannot be in the anonymous namespace. +static std::ostream& operator<<(std::ostream& out, + const std::variant& event) { + std::visit([&](const auto& e) { out << e; }, event); + return out; +} + +static std::ostream& operator<<(std::ostream& out, + const InputTraceSession::WindowDispatchEvent& event) { + out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event; + return out; +} + +namespace { + +inline uint32_t getId(const std::variant& event) { + return std::visit([&](const auto& e) { return e.getId(); }, event); +} + +std::unique_ptr startTrace( + const std::function&)>& configure) { + protozero::HeapBuffered inputEventConfig{}; + configure(inputEventConfig); + + perfetto::TraceConfig config; + config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB. + auto* dataSourceConfig = config.add_data_sources()->mutable_config(); + dataSourceConfig->set_name("android.input.inputevent"); + dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString()); + + std::unique_ptr tracingSession(perfetto::Tracing::NewTrace()); + tracingSession->Setup(config); + tracingSession->StartBlocking(); + return tracingSession; +} + +std::string stopTrace(std::unique_ptr tracingSession) { + tracingSession->StopBlocking(); + std::vector traceChars(tracingSession->ReadTraceBlocking()); + return {traceChars.data(), traceChars.size()}; +} + +// Decodes the trace, and returns all of the traced input events, and whether they were each +// traced as a redacted event. +auto decodeTrace(const std::string& rawTrace) { + using namespace perfetto::protos::pbzero; + + ArrayMap tracedMotions; + ArrayMap tracedKeys; + ArrayMap tracedWindowDispatches; + + Trace::Decoder trace{rawTrace}; + if (trace.has_packet()) { + auto it = trace.packet(); + while (it) { + TracePacket::Decoder packet{it->as_bytes()}; + if (packet.has_android_input_event()) { + AndroidInputEvent::Decoder event{packet.android_input_event()}; + if (event.has_dispatcher_motion_event()) { + tracedMotions.emplace_back(event.dispatcher_motion_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_motion_event_redacted()) { + tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_key_event()) { + tracedKeys.emplace_back(event.dispatcher_key_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_key_event_redacted()) { + tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_window_dispatch_event()) { + tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_window_dispatch_event_redacted()) { + tracedWindowDispatches + .emplace_back(event.dispatcher_window_dispatch_event_redacted(), + /*redacted=*/true); + } + } + it++; + } + } + return std::tuple{std::move(tracedMotions), std::move(tracedKeys), + std::move(tracedWindowDispatches)}; +} + +bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) { + return static_cast(expected.getId()) == traced.event_id(); +} + +bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) { + return static_cast(expected.getId()) == traced.event_id(); +} + +bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected, + const AndroidWindowInputDispatchEvent::Decoder& traced) { + return static_cast(getId(expected.event)) == traced.event_id() && + expected.window->getId() == traced.window_id(); +} + +template +void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents, + const TracedEvents& tracedEvents, std::string_view name) { + uint32_t totalExpectedCount = 0; + + for (const auto& [expectedEvent, expectedLevel] : expectedEvents) { + int32_t totalMatchCount = 0; + int32_t redactedMatchCount = 0; + for (const auto& [tracedEvent, isRedacted] : tracedEvents) { + if (eventMatches(expectedEvent, tracedEvent)) { + totalMatchCount++; + if (isRedacted) { + redactedMatchCount++; + } + } + } + switch (expectedLevel) { + case Level::NONE: + ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced" + << "\n\tExpected event: " << expectedEvent; + break; + case Level::REDACTED: + case Level::COMPLETE: + ASSERT_EQ(totalMatchCount, 1) + << "Event should match exactly one traced event, but it matched: " + << totalMatchCount << "\n\tExpected event: " << expectedEvent; + ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0); + totalExpectedCount++; + break; + } + } + + ASSERT_EQ(tracedEvents.size(), totalExpectedCount) + << "The number of traced " << name + << " events does not exactly match the number of expected events"; +} + +} // namespace + +InputTraceSession::InputTraceSession( + std::function&)> configure) + : mPerfettoSession(startTrace(std::move(configure))) {} + +InputTraceSession::~InputTraceSession() { + const auto rawTrace = stopTrace(std::move(mPerfettoSession)); + verifyExpectations(rawTrace); +} + +void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) { + mExpectedMotions.emplace_back(event, level); +} + +void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) { + mExpectedKeys.emplace_back(event, level); +} + +void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) { + mExpectedWindowDispatches.emplace_back(event, level); +} + +void InputTraceSession::verifyExpectations(const std::string& rawTrace) { + auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace); + + verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion"); + verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key"); + verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches, + "window dispatch"); +} + +} // namespace android diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h new file mode 100644 index 0000000000..ed20bc8343 --- /dev/null +++ b/services/inputflinger/tests/InputTraceSession.h @@ -0,0 +1,85 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "FakeWindows.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +/** + * Tracing level constants used for adding expectations to the InputTraceSession. + */ +enum class Level { + NONE, + REDACTED, + COMPLETE, +}; + +template +using ArrayMap = std::vector>; + +/** + * A scoped representation of a tracing session that is used to make assertions on the trace. + * + * When the trace session is created, an "android.input.inputevent" trace will be started + * synchronously with the given configuration. While the trace is ongoing, the caller must + * specify the events that are expected to be in the trace using the expect* methods. + * + * When the session is destroyed, the trace is stopped synchronously, and all expectations will + * be verified using the gtest framework. This acts as a strict verifier, where the verification + * will fail both if an expected event does not show up in the trace and if there is an extra + * event in the trace that was not expected. Ordering is NOT verified for any events. + */ +class InputTraceSession { +public: + explicit InputTraceSession( + std::function&)> + configure); + + ~InputTraceSession(); + + void expectMotionTraced(Level level, const MotionEvent& event); + + void expectKeyTraced(Level level, const KeyEvent& event); + + struct WindowDispatchEvent { + std::variant event; + sp window; + }; + void expectDispatchTraced(Level level, const WindowDispatchEvent& event); + +private: + std::unique_ptr mPerfettoSession; + ArrayMap mExpectedWindowDispatches; + ArrayMap mExpectedMotions; + ArrayMap mExpectedKeys; + + void verifyExpectations(const std::string& rawTrace); +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp new file mode 100644 index 0000000000..fe4d6d9a8f --- /dev/null +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -0,0 +1,732 @@ +/* + * Copyright 2024 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. + */ + +#include "../InputCommonConverter.h" +#include "../dispatcher/InputDispatcher.h" +#include "../dispatcher/trace/InputTracingPerfettoBackend.h" +#include "../dispatcher/trace/ThreadedBackend.h" +#include "FakeApplicationHandle.h" +#include "FakeInputDispatcherPolicy.h" +#include "FakeWindows.h" +#include "InputTraceSession.h" +#include "TestEventMatchers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android::inputdispatcher::trace { + +using perfetto::protos::pbzero::AndroidInputEventConfig; + +namespace { + +constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; + +// Ensure common actions are interchangeable between keys and motions for convenience. +static_assert(static_cast(AMOTION_EVENT_ACTION_DOWN) == + static_cast(AKEY_EVENT_ACTION_DOWN)); +static_assert(static_cast(AMOTION_EVENT_ACTION_UP) == + static_cast(AKEY_EVENT_ACTION_UP)); +constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; +constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; + +constexpr gui::Pid PID{1}; + +constexpr gui::Uid ALLOWED_UID_1{10012}; +constexpr gui::Uid ALLOWED_UID_2{10013}; +constexpr gui::Uid DISALLOWED_UID_1{1}; +constexpr gui::Uid DISALLOWED_UID_2{99}; +constexpr gui::Uid UNLISTED_UID{12345}; + +const std::string ALLOWED_PKG_1{"allowed.pkg.1"}; +const std::string ALLOWED_PKG_2{"allowed.pkg.2"}; +const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"}; +const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"}; + +const std::shared_ptr APP = std::make_shared(); + +} // namespace + +// --- InputTracingTest --- + +class InputTracingTest : public testing::Test { +protected: + std::unique_ptr mFakePolicy; + std::unique_ptr mDispatcher; + + void SetUp() override { + impl::PerfettoBackend::sUseInProcessBackendForTest = true; + + mFakePolicy = std::make_unique(); + mFakePolicy->addPackageUidMapping(ALLOWED_PKG_1, ALLOWED_UID_1); + mFakePolicy->addPackageUidMapping(ALLOWED_PKG_2, ALLOWED_UID_2); + mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_1, DISALLOWED_UID_1); + mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_2, DISALLOWED_UID_2); + + auto tracingBackend = std::make_unique>( + impl::PerfettoBackend([this](const auto& pkg) { + return static_cast(*mFakePolicy) + .getPackageUid(pkg); + })); + mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting(); + mDispatcher = std::make_unique(*mFakePolicy, std::move(tracingBackend)); + + mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); + ASSERT_EQ(OK, mDispatcher->start()); + } + + void TearDown() override { + ASSERT_EQ(OK, mDispatcher->stop()); + mDispatcher.reset(); + mFakePolicy.reset(); + } + + void waitForTracerIdle() { + mDispatcher->waitForIdle(); + mRequestTracerIdle(); + } + + void setFocusedWindow(const sp& window) { + gui::FocusRequest request; + request.token = window->getToken(); + request.windowName = window->getName(); + request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + request.displayId = window->getInfo()->displayId; + mDispatcher->setFocusedWindow(request); + } + + void tapAndExpect(const std::vector>& windows, + Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) { + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(down); + s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down)); + for (const auto& window : windows) { + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + + const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(up); + s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up)); + for (const auto& window : windows) { + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + } + + void keypressAndExpect(const std::vector>& windows, + Level inboundTraceLevel, Level dispatchTraceLevel, + InputTraceSession& s) { + const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build(); + mDispatcher->notifyKey(down); + s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down)); + for (const auto& window : windows) { + auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + + const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build(); + mDispatcher->notifyKey(up); + s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up)); + for (const auto& window : windows) { + auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + } + +private: + std::function mRequestTracerIdle; +}; + +TEST_F(InputTracingTest, EmptyConfigTracesNothing) { + InputTraceSession s{[](auto& config) {}}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + keypressAndExpect({window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, TraceAll) { + InputTraceSession s{ + [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, NoRulesTracesNothing) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + keypressAndExpect({window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, EmptyRuleMatchesEverything) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match everything as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, UnspecifiedTracelLevel) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match everything, trace level unspecified + auto rule = config->add_rules(); + }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + // Event is not traced by default if trace level is unspecified + tapAndExpect({window}, Level::NONE, Level::NONE, s); + keypressAndExpect({window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchSecureWindow) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match secure windows as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->set_match_secure(true); + }}; + + // Add a normal window and a spy window. + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + auto spy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Since neither are secure windows, events should not be traced. + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + // Events should be matched as secure if any of the target windows is marked as secure. + spy->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setSecure(false); + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setSecure(true); + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setSecure(false); + window->setSecure(false); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchImeConnectionActive) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match IME Connection Active as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->set_match_ime_connection_active(true); + }}; + + // Add a normal window and a spy window. + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + auto spy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Since IME connection is not active, events should not be traced. + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + mDispatcher->setInputMethodConnectionIsActive(true); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + mDispatcher->setInputMethodConnectionIsActive(false); + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchAllPackages) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match all package as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->add_match_all_packages(ALLOWED_PKG_1); + rule->add_match_all_packages(ALLOWED_PKG_2); + }}; + + // All windows are allowlisted. + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + auto spy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_2); + spy->setSpy(true); + spy->setTrustedOverlay(true); + auto systemSpy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM}); + systemSpy->setSpy(true); + systemSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged( + {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + // Add a disallowed spy. This will result in the event not being traced for all windows. + auto disallowedSpy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1); + disallowedSpy->setSpy(true); + disallowedSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(), + *disallowedSpy->getInfo(), *window->getInfo()}, + {}, + 0, + 0}); + + tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Change the owner of the disallowed spy to one for which we don't have a package mapping. + disallowedSpy->setOwnerInfo(PID, UNLISTED_UID); + mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(), + *disallowedSpy->getInfo(), *window->getInfo()}, + {}, + 0, + 0}); + + tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Remove the disallowed spy. Events are traced again. + mDispatcher->onWindowInfosChanged( + {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchAnyPackages) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match any package as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->add_match_any_packages(ALLOWED_PKG_1); + rule->add_match_any_packages(ALLOWED_PKG_2); + }}; + + // Just a disallowed window. Events are not traced. + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, DISALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // Add a spy for which we don't have a package mapping. Events are still not traced. + auto disallowedSpy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy->setOwnerInfo(PID, UNLISTED_UID); + disallowedSpy->setSpy(true); + disallowedSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Add an allowed spy. Events are now traced for all packages. + auto spy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_1); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged( + {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + // Add another disallowed spy. Events are still traced. + auto disallowedSpy2 = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2); + disallowedSpy2->setSpy(true); + disallowedSpy2->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(), + *spy->getInfo(), *window->getInfo()}, + {}, + 0, + 0}); + + tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MultipleMatchersInOneRule) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match all of the following conditions as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->add_match_all_packages(ALLOWED_PKG_1); + rule->add_match_all_packages(ALLOWED_PKG_2); + rule->add_match_any_packages(ALLOWED_PKG_1); + rule->add_match_any_packages(DISALLOWED_PKG_1); + rule->set_match_secure(false); + rule->set_match_ime_connection_active(false); + }}; + + // A single window into an allowed UID. Matches all matchers. + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + // Secure window does not match. + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // IME Connection Active does not match. + window->setSecure(false); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + mDispatcher->setInputMethodConnectionIsActive(true); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages. + mDispatcher->setInputMethodConnectionIsActive(false); + auto disallowedSpy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1); + disallowedSpy->setSpy(true); + disallowedSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages. + window->setOwnerInfo(PID, ALLOWED_UID_2); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // All conditions match. + auto spy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_1); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MultipleRulesMatchInOrder) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Don't trace secure events + auto rule1 = config->add_rules(); + rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE); + rule1->set_match_secure(true); + // Rule: Trace matched packages as COMPLETE when IME inactive + auto rule2 = config->add_rules(); + rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule2->add_match_all_packages(ALLOWED_PKG_1); + rule2->add_match_all_packages(ALLOWED_PKG_2); + rule2->set_match_ime_connection_active(false); + // Rule: Trace the rest of the events as REDACTED + auto rule3 = config->add_rules(); + rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + // Verify that the first rule that matches in the order that they are specified is the + // one that applies to the event. + mDispatcher->setInputMethodConnectionIsActive(true); + tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s); + + mDispatcher->setInputMethodConnectionIsActive(false); + auto spy = sp::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_2); + spy->setSpy(true); + spy->setTrustedOverlay(true); + spy->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + spy->setSecure(false); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setOwnerInfo(PID, DISALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, TraceInboundEvents) { + InputTraceSession s{[](auto& config) { + // Only trace inbounds events - don't trace window dispatch + config->set_trace_dispatcher_input_events(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace everything as REDACTED + auto rule1 = config->add_rules(); + rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + // Only the inbound events are traced. No dispatch events are traced. + tapAndExpect({window}, Level::REDACTED, Level::NONE, s); + + // Notify a down event, which should be traced. + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + s.expectMotionTraced(Level::REDACTED, toMotionEvent(down)); + mDispatcher->notifyMotion(down); + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s.expectDispatchTraced(Level::NONE, {*consumed, window}); + + // Force a cancel event to be synthesized. This should not be traced, because only inbound + // events are requested. + mDispatcher->cancelCurrentTouch(); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + s.expectMotionTraced(Level::NONE, *consumed); + s.expectDispatchTraced(Level::NONE, {*consumed, window}); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, TraceWindowDispatch) { + InputTraceSession s{[](auto& config) { + // Only trace window dispatch - don't trace event details + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace everything as REDACTED + auto rule1 = config->add_rules(); + rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + }}; + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + // Only dispatch events are traced. No inbound events are traced. + tapAndExpect({window}, Level::NONE, Level::REDACTED, s); + + // Notify a down event; the dispatch should be traced. + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + s.expectMotionTraced(Level::NONE, toMotionEvent(down)); + mDispatcher->notifyMotion(down); + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s.expectDispatchTraced(Level::REDACTED, {*consumed, window}); + + // Force a cancel event to be synthesized. All events that are dispatched should be traced. + mDispatcher->cancelCurrentTouch(); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + s.expectMotionTraced(Level::NONE, *consumed); + s.expectDispatchTraced(Level::REDACTED, {*consumed, window}); +} + +TEST_F(InputTracingTest, SimultaneousTracingSessions) { + auto s1 = std::make_unique( + [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }); + + auto window = sp::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + + auto s2 = std::make_unique([](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace all events as REDACTED when IME inactive + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + rule->set_match_ime_connection_active(false); + }); + + auto s3 = std::make_unique([](auto& config) { + // Only trace window dispatch + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace non-secure events as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->set_match_secure(false); + }); + + // Down event should be recorded on all traces. + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(down); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down)); + s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(down)); + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s2->expectDispatchTraced(Level::REDACTED, {*consumed, window}); + s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + + // Move event when IME is active. + mDispatcher->setInputMethodConnectionIsActive(true); + const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(move1); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1)); + s2->expectMotionTraced(Level::NONE, toMotionEvent(move1)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(move1)); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s2->expectDispatchTraced(Level::NONE, {*consumed, window}); + s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + + // Move event after window became secure. + mDispatcher->setInputMethodConnectionIsActive(false); + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(move2); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2)); + s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(move2)); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s2->expectDispatchTraced(Level::REDACTED, {*consumed, window}); + s3->expectDispatchTraced(Level::NONE, {*consumed, window}); + + waitForTracerIdle(); + s2.reset(); + + // Up event. + window->setSecure(false); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(up); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(up)); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + + waitForTracerIdle(); + s3.reset(); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + + waitForTracerIdle(); + s1.reset(); +} + +} // namespace android::inputdispatcher::trace -- GitLab From 01b93b1317b880e4a205e9d4903b6e9f1d277d7f Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Wed, 3 Apr 2024 21:27:34 +0000 Subject: [PATCH 197/465] Re-enable RE test ..._premultipliesAlpha on Graphite w/ tolerance 1 Ganesh rounds this more consistently than Graphite, but Graphite's approach is technically more correct. See egdaniel@'s description at b/331446496#comment5 Bug: b/331446496 Change-Id: Ib1fd7975f36ccc114cef3b0cdd60ac173df62a69 Test: librenderengine_test --- libs/renderengine/tests/RenderEngineTest.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 4bd0852b05..4e5440f5ae 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -1265,7 +1265,12 @@ void RenderEngineTest::fillRedBufferWithPremultiplyAlpha() { void RenderEngineTest::fillBufferWithPremultiplyAlpha() { fillRedBufferWithPremultiplyAlpha(); - expectBufferColor(fullscreenRect(), 128, 0, 0, 128); + // Different backends and GPUs may round 255 * 0.5 = 127.5 differently, but + // either 127 or 128 are acceptable. Checking both 127 and 128 with a + // tolerance of 1 allows either 127 or 128 to pass, while preventing 126 or + // 129 from erroneously passing. + expectBufferColor(fullscreenRect(), 127, 0, 0, 127, 1); + expectBufferColor(fullscreenRect(), 128, 0, 0, 128, 1); } void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() { @@ -2061,11 +2066,6 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) { } TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) { - // TODO: b/331446496 - Fix in Graphite and re-enable. - if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) { - GTEST_SKIP(); - } - if (!GetParam()->apiSupported()) { GTEST_SKIP(); } -- GitLab From 4cc5459215e6d74beca74631d9eb70c332540d62 Mon Sep 17 00:00:00 2001 From: Julien Desprez Date: Wed, 14 Feb 2024 04:14:59 +0000 Subject: [PATCH 198/465] Add RootTargetPreparer for explicitly specifying the root dependency Before, the test gets root implicitly via DeviceSetup which wasn't intented. Test: presubmit Bug: 331655690 (cherry picked from https://android-review.googlesource.com/q/commit:5ed9e351487e45fd7c4a728e555aa8b4ce870829) Merged-In: Ia81ba888aca96ca626c2a2464e3f0e9146bae231 Change-Id: Ia81ba888aca96ca626c2a2464e3f0e9146bae231 --- libs/gui/tests/AndroidTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/gui/tests/AndroidTest.xml b/libs/gui/tests/AndroidTest.xml index 5e09fff6bb..760a30a843 100644 --- a/libs/gui/tests/AndroidTest.xml +++ b/libs/gui/tests/AndroidTest.xml @@ -18,6 +18,7 @@

and another type Q. This allows the use of NonNull

in sorted containers like std::set, and allows easier lookup via unwrapped pointers. * Specialize std::hash for NonNull. This allows the use of NonNull

in hashed containers like std::unordered_set. Test: atest ftl_test Bug: 185536303 Change-Id: Ib1090e393ea5f5641be2cd9c61011049039ea615 --- include/ftl/non_null.h | 112 ++++++++++++++++++++++++++++++++++--- libs/ftl/non_null_test.cpp | 86 +++++++++++++++++++++++++++- 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h index 35d09d71de..4a5d8bffd0 100644 --- a/include/ftl/non_null.h +++ b/include/ftl/non_null.h @@ -68,26 +68,28 @@ class NonNull final { constexpr NonNull(const NonNull&) = default; constexpr NonNull& operator=(const NonNull&) = default; - constexpr const Pointer& get() const { return pointer_; } - constexpr explicit operator const Pointer&() const { return get(); } + [[nodiscard]] constexpr const Pointer& get() const { return pointer_; } + [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); } // Move operations. These break the invariant, so care must be taken to avoid subsequent access. constexpr NonNull(NonNull&&) = default; constexpr NonNull& operator=(NonNull&&) = default; - constexpr Pointer take() && { return std::move(pointer_); } - constexpr explicit operator Pointer() && { return take(); } + [[nodiscard]] constexpr Pointer take() && { return std::move(pointer_); } + [[nodiscard]] constexpr explicit operator Pointer() && { return take(); } // Dereferencing. - constexpr decltype(auto) operator*() const { return *get(); } - constexpr decltype(auto) operator->() const { return get(); } + [[nodiscard]] constexpr decltype(auto) operator*() const { return *get(); } + [[nodiscard]] constexpr decltype(auto) operator->() const { return get(); } + + [[nodiscard]] constexpr explicit operator bool() const { return !(pointer_ == nullptr); } // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions // through the passkey idiom, for clear compilation errors. template constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward

(pointer)) { - if (!pointer_) std::abort(); + if (pointer_ == nullptr) std::abort(); } private: @@ -98,11 +100,13 @@ class NonNull final { }; template -constexpr auto as_non_null(P&& pointer) -> NonNull> { +[[nodiscard]] constexpr auto as_non_null(P&& pointer) -> NonNull> { using Passkey = typename NonNull>::Passkey; return {Passkey{}, std::forward

(pointer)}; } +// NonNull

<=> NonNull + template constexpr bool operator==(const NonNull

& lhs, const NonNull& rhs) { return lhs.get() == rhs.get(); @@ -113,4 +117,96 @@ constexpr bool operator!=(const NonNull

& lhs, const NonNull& rhs) { return !operator==(lhs, rhs); } +template +constexpr bool operator<(const NonNull

& lhs, const NonNull& rhs) { + return lhs.get() < rhs.get(); +} + +template +constexpr bool operator<=(const NonNull

& lhs, const NonNull& rhs) { + return lhs.get() <= rhs.get(); +} + +template +constexpr bool operator>=(const NonNull

& lhs, const NonNull& rhs) { + return lhs.get() >= rhs.get(); +} + +template +constexpr bool operator>(const NonNull

& lhs, const NonNull& rhs) { + return lhs.get() > rhs.get(); +} + +// NonNull

<=> Q + +template +constexpr bool operator==(const NonNull

& lhs, const Q& rhs) { + return lhs.get() == rhs; +} + +template +constexpr bool operator!=(const NonNull

& lhs, const Q& rhs) { + return lhs.get() != rhs; +} + +template +constexpr bool operator<(const NonNull

& lhs, const Q& rhs) { + return lhs.get() < rhs; +} + +template +constexpr bool operator<=(const NonNull

& lhs, const Q& rhs) { + return lhs.get() <= rhs; +} + +template +constexpr bool operator>=(const NonNull

& lhs, const Q& rhs) { + return lhs.get() >= rhs; +} + +template +constexpr bool operator>(const NonNull

& lhs, const Q& rhs) { + return lhs.get() > rhs; +} + +// P <=> NonNull + +template +constexpr bool operator==(const P& lhs, const NonNull& rhs) { + return lhs == rhs.get(); +} + +template +constexpr bool operator!=(const P& lhs, const NonNull& rhs) { + return lhs != rhs.get(); +} + +template +constexpr bool operator<(const P& lhs, const NonNull& rhs) { + return lhs < rhs.get(); +} + +template +constexpr bool operator<=(const P& lhs, const NonNull& rhs) { + return lhs <= rhs.get(); +} + +template +constexpr bool operator>=(const P& lhs, const NonNull& rhs) { + return lhs >= rhs.get(); +} + +template +constexpr bool operator>(const P& lhs, const NonNull& rhs) { + return lhs > rhs.get(); +} + } // namespace android::ftl + +// Specialize std::hash for ftl::NonNull +template +struct std::hash> { + std::size_t operator()(const android::ftl::NonNull

& ptr) const { + return std::hash

()(ptr.get()); + } +}; diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp index bd0462b3b6..367b398915 100644 --- a/libs/ftl/non_null_test.cpp +++ b/libs/ftl/non_null_test.cpp @@ -14,12 +14,17 @@ * limitations under the License. */ +#include #include #include #include +#include #include #include +#include +#include +#include namespace android::test { namespace { @@ -47,7 +52,7 @@ Pair dupe_if(ftl::NonNull> non_null_ptr, bool condition) { // Keep in sync with example usage in header file. TEST(NonNull, Example) { const auto string_ptr = ftl::as_non_null(std::make_shared("android")); - std::size_t size; + std::size_t size{}; get_length(string_ptr, ftl::as_non_null(&size)); EXPECT_EQ(size, 7u); @@ -71,5 +76,84 @@ constexpr StringViewPtr longest(StringViewPtr ptr1, StringViewPtr ptr2) { static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr); +static_assert(static_cast(kApplePtr)); + +static_assert(std::is_same_v())), + ftl::NonNull>); + } // namespace + +TEST(NonNull, SwapRawPtr) { + int i1 = 123; + int i2 = 456; + auto ptr1 = ftl::as_non_null(&i1); + auto ptr2 = ftl::as_non_null(&i2); + + std::swap(ptr1, ptr2); + + EXPECT_EQ(*ptr1, 456); + EXPECT_EQ(*ptr2, 123); +} + +TEST(NonNull, SwapSmartPtr) { + auto ptr1 = ftl::as_non_null(std::make_shared(123)); + auto ptr2 = ftl::as_non_null(std::make_shared(456)); + + std::swap(ptr1, ptr2); + + EXPECT_EQ(*ptr1, 456); + EXPECT_EQ(*ptr2, 123); +} + +TEST(NonNull, VectorOfRawPtr) { + int i = 1; + std::vector> vpi; + vpi.push_back(ftl::as_non_null(&i)); + EXPECT_FALSE(ftl::contains(vpi, nullptr)); + EXPECT_TRUE(ftl::contains(vpi, &i)); + EXPECT_TRUE(ftl::contains(vpi, vpi.front())); +} + +TEST(NonNull, VectorOfSmartPtr) { + std::vector>> vpi; + vpi.push_back(ftl::as_non_null(std::make_shared(2))); + EXPECT_FALSE(ftl::contains(vpi, nullptr)); + EXPECT_TRUE(ftl::contains(vpi, vpi.front().get())); + EXPECT_TRUE(ftl::contains(vpi, vpi.front())); +} + +TEST(NonNull, SetOfRawPtr) { + int i = 1; + std::set> spi; + spi.insert(ftl::as_non_null(&i)); + EXPECT_FALSE(ftl::contains(spi, nullptr)); + EXPECT_TRUE(ftl::contains(spi, &i)); + EXPECT_TRUE(ftl::contains(spi, *spi.begin())); +} + +TEST(NonNull, SetOfSmartPtr) { + std::set>> spi; + spi.insert(ftl::as_non_null(std::make_shared(2))); + EXPECT_FALSE(ftl::contains(spi, nullptr)); + EXPECT_TRUE(ftl::contains(spi, spi.begin()->get())); + EXPECT_TRUE(ftl::contains(spi, *spi.begin())); +} + +TEST(NonNull, UnorderedSetOfRawPtr) { + int i = 1; + std::unordered_set> spi; + spi.insert(ftl::as_non_null(&i)); + EXPECT_FALSE(ftl::contains(spi, nullptr)); + EXPECT_TRUE(ftl::contains(spi, &i)); + EXPECT_TRUE(ftl::contains(spi, *spi.begin())); +} + +TEST(NonNull, UnorderedSetOfSmartPtr) { + std::unordered_set>> spi; + spi.insert(ftl::as_non_null(std::make_shared(2))); + EXPECT_FALSE(ftl::contains(spi, nullptr)); + EXPECT_TRUE(ftl::contains(spi, spi.begin()->get())); + EXPECT_TRUE(ftl::contains(spi, *spi.begin())); +} + } // namespace android::test -- GitLab From 28449b7801d71a1f76fc0410aee1c28e604b61c9 Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Thu, 2 May 2024 13:36:49 +0000 Subject: [PATCH 294/465] Fix flaky test: increase sleep duration Test: atest --host libinputflinger_rs_test Bug: 337266577 Change-Id: I8ffe4bd138ebf5f8bce748901cd7bd7d19ad93b9 --- services/inputflinger/rust/input_filter_thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs index 96e5681ead..34f9b251c9 100644 --- a/services/inputflinger/rust/input_filter_thread.rs +++ b/services/inputflinger/rust/input_filter_thread.rs @@ -309,7 +309,7 @@ mod tests { let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds(); test_thread.request_timeout_at_time((now + 10) * 1000000); - std::thread::sleep(Duration::from_millis(20)); + std::thread::sleep(Duration::from_millis(100)); assert!(test_thread_callback.is_notify_timeout_called()); } -- GitLab From 70f49095025be9deccfad3f088d9095964f9c785 Mon Sep 17 00:00:00 2001 From: Rachel Lee Date: Thu, 2 May 2024 13:53:51 -0700 Subject: [PATCH 295/465] More log for frequent frames Bug: 337072550 Test: presubmit Change-Id: Ibdad9d520deb9bfaf1707e4c8da5c7be452d2613 --- services/surfaceflinger/Scheduler/LayerInfo.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp index 21b7247227..632f42ab36 100644 --- a/services/surfaceflinger/Scheduler/LayerInfo.cpp +++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp @@ -181,19 +181,19 @@ bool LayerInfo::isAnimating(nsecs_t now) const { bool LayerInfo::hasEnoughDataForHeuristic() const { // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates if (mFrameTimes.size() < 2) { - ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size()); + ALOGV("%s fewer than 2 frames recorded: %zu", mName.c_str(), mFrameTimes.size()); return false; } if (!isFrameTimeValid(mFrameTimes.front())) { - ALOGV("stale frames still captured"); + ALOGV("%s stale frames still captured", mName.c_str()); return false; } const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime; if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) { - ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(), - totalDuration / 1e9f); + ALOGV("%s not enough frames captured: %zu | %.2f seconds", mName.c_str(), + mFrameTimes.size(), totalDuration / 1e9f); return false; } @@ -365,6 +365,8 @@ LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelec } if (frequent.clearHistory) { + ATRACE_FORMAT_INSTANT("frequent.clearHistory"); + ALOGV("%s frequent.clearHistory", mName.c_str()); clearHistory(now); } -- GitLab From ce0fe52db2fca772168b0ded74a3779350a945a7 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Fri, 3 May 2024 01:54:56 +0000 Subject: [PATCH 296/465] Revert^2 "[legacy sf flag] - Remove legacyFrontEndEnabled" This reverts commit ce6bde8dfa93038dd1063bae10b1f83eb1bc7e97. Reason for revert: b/337894453 is now fixed and the original changes from ag/27000992 should no longer cause a regression in arc.PlayStore.betty_vm Change-Id: I888a16b3137ba8f81d19b89c8ff291f4adf7d601 --- services/surfaceflinger/Layer.cpp | 4 - services/surfaceflinger/SurfaceFlinger.cpp | 190 +++++++----------- services/surfaceflinger/SurfaceFlinger.h | 1 - .../SurfaceFlinger_ColorMatrixTest.cpp | 2 + .../tests/unittests/TestableSurfaceFlinger.h | 4 + 5 files changed, 82 insertions(+), 119 deletions(-) diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index e7d2a11e7b..c56567b286 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3218,10 +3218,6 @@ bool Layer::setBuffer(std::shared_ptr& buffer, mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(), mOwnerUid, postTime, getGameMode()); - if (mFlinger->mLegacyFrontEndEnabled) { - recordLayerHistoryBufferUpdate(getLayerProps(), systemTime()); - } - setFrameTimelineVsyncForBufferTransaction(info, postTime); if (dequeueTime && *dequeueTime != 0) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 14dfdf536c..526e63c637 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -531,8 +531,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI mLayerLifecycleManagerEnabled = base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true); - mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled || - base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false); // These are set by the HWC implementation to indicate that they will use the workarounds. mIsHotplugErrViaNegVsync = @@ -2440,86 +2438,84 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0; bool newDataLatched = false; - if (!mLegacyFrontEndEnabled) { - ATRACE_NAME("DisplayCallbackAndStatsUpdates"); - mustComposite |= applyTransactionsLocked(update.transactions, vsyncId); - traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); }); - const nsecs_t latchTime = systemTime(); - bool unused = false; - - for (auto& layer : mLayerLifecycleManager.getLayers()) { - if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) && - layer->bgColorLayer) { - sp bgColorLayer = getFactory().createEffectLayer( - LayerCreationArgs(this, nullptr, layer->name, - ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(), - std::make_optional(layer->id), true)); - mLegacyLayers[bgColorLayer->sequence] = bgColorLayer; - } - const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch(); - - auto it = mLegacyLayers.find(layer->id); - if (it == mLegacyLayers.end() && - layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) { - // Layer handle was created and immediately destroyed. It was destroyed before it - // was added to the map. - continue; - } - - LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(), - "Couldnt find layer object for %s", - layer->getDebugString().c_str()); - if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) { - if (!it->second->hasBuffer()) { - // The last latch time is used to classify a missed frame as buffer stuffing - // instead of a missed frame. This is used to identify scenarios where we - // could not latch a buffer or apply a transaction due to backpressure. - // We only update the latch time for buffer less layers here, the latch time - // is updated for buffer layers when the buffer is latched. - it->second->updateLastLatchTime(latchTime); - } - continue; - } + ATRACE_NAME("DisplayCallbackAndStatsUpdates"); + mustComposite |= applyTransactionsLocked(update.transactions, vsyncId); + traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); }); + const nsecs_t latchTime = systemTime(); + bool unused = false; + + for (auto& layer : mLayerLifecycleManager.getLayers()) { + if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) && + layer->bgColorLayer) { + sp bgColorLayer = getFactory().createEffectLayer( + LayerCreationArgs(this, nullptr, layer->name, + ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(), + std::make_optional(layer->id), true)); + mLegacyLayers[bgColorLayer->sequence] = bgColorLayer; + } + const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch(); + + auto it = mLegacyLayers.find(layer->id); + if (it == mLegacyLayers.end() && + layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) { + // Layer handle was created and immediately destroyed. It was destroyed before it + // was added to the map. + continue; + } - const bool bgColorOnly = - !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID); - if (willReleaseBufferOnLatch) { - mLayersWithBuffersRemoved.emplace(it->second); + LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(), + "Couldnt find layer object for %s", + layer->getDebugString().c_str()); + if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) { + if (!it->second->hasBuffer()) { + // The last latch time is used to classify a missed frame as buffer stuffing + // instead of a missed frame. This is used to identify scenarios where we + // could not latch a buffer or apply a transaction due to backpressure. + // We only update the latch time for buffer less layers here, the latch time + // is updated for buffer layers when the buffer is latched. + it->second->updateLastLatchTime(latchTime); } - it->second->latchBufferImpl(unused, latchTime, bgColorOnly); - newDataLatched = true; + continue; + } - mLayersWithQueuedFrames.emplace(it->second); - mLayersIdsWithQueuedFrames.emplace(it->second->sequence); + const bool bgColorOnly = + !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID); + if (willReleaseBufferOnLatch) { + mLayersWithBuffersRemoved.emplace(it->second); } + it->second->latchBufferImpl(unused, latchTime, bgColorOnly); + newDataLatched = true; - updateLayerHistory(latchTime); - mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) { - if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) == - mLayersIdsWithQueuedFrames.end()) - return; - Region visibleReg; - visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion); - invalidateLayerStack(snapshot.outputFilter, visibleReg); - }); + mLayersWithQueuedFrames.emplace(it->second); + mLayersIdsWithQueuedFrames.emplace(it->second->sequence); + } - for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) { - mLegacyLayers.erase(destroyedLayer->id); - } + updateLayerHistory(latchTime); + mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) { + if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) == mLayersIdsWithQueuedFrames.end()) + return; + Region visibleReg; + visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion); + invalidateLayerStack(snapshot.outputFilter, visibleReg); + }); - { - ATRACE_NAME("LLM:commitChanges"); - mLayerLifecycleManager.commitChanges(); - } + for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) { + mLegacyLayers.erase(destroyedLayer->id); + } - // enter boot animation on first buffer latch - if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) { - ALOGI("Enter boot animation"); - mBootStage = BootStage::BOOTANIMATION; - } + { + ATRACE_NAME("LLM:commitChanges"); + mLayerLifecycleManager.commitChanges(); + } + + // enter boot animation on first buffer latch + if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) { + ALOGI("Enter boot animation"); + mBootStage = BootStage::BOOTANIMATION; } + mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched; - if (mustComposite && !mLegacyFrontEndEnabled) { + if (mustComposite) { commitTransactions(); } @@ -2621,12 +2617,7 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, mScheduler->getPacesetterRefreshRate()); const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded); - bool transactionsAreEmpty; - if (mLegacyFrontEndEnabled) { - mustComposite |= - updateLayerSnapshotsLegacy(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(), - flushTransactions, transactionsAreEmpty); - } + bool transactionsAreEmpty = false; if (mLayerLifecycleManagerEnabled) { mustComposite |= updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(), @@ -5250,16 +5241,9 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin nsecs_t now = systemTime(); uint32_t clientStateFlags = 0; for (auto& resolvedState : states) { - if (mLegacyFrontEndEnabled) { - clientStateFlags |= - setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime, - isAutoTimestamp, postTime, transactionId); - - } else /*mLayerLifecycleManagerEnabled*/ { - clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, - desiredPresentTime, isAutoTimestamp, - postTime, transactionId); - } + clientStateFlags |= + updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, desiredPresentTime, + isAutoTimestamp, postTime, transactionId); if (!mLayerLifecycleManagerEnabled) { if ((flags & eAnimation) && resolvedState.state.surface) { if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) { @@ -5331,7 +5315,7 @@ bool SurfaceFlinger::applyAndCommitDisplayTransactionStatesLocked( } mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded; - if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) { + if (mFrontEndDisplayInfosChanged) { processDisplayChangesLocked(); mFrontEndDisplayInfos.clear(); for (const auto& [_, display] : mDisplays) { @@ -5974,11 +5958,6 @@ status_t SurfaceFlinger::mirrorDisplay(DisplayId displayId, const LayerCreationA return result; } - if (mLegacyFrontEndEnabled) { - std::scoped_lock lock(mMirrorDisplayLock); - mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client); - } - setTransactionFlags(eTransactionFlushNeeded); return NO_ERROR; } @@ -6093,9 +6072,7 @@ void SurfaceFlinger::initializeDisplays() { std::vector transactions; transactions.emplace_back(state); - if (mLegacyFrontEndEnabled) { - applyTransactions(transactions, VsyncId{0}); - } else { + { Mutex::Autolock lock(mStateLock); applyAndCommitDisplayTransactionStatesLocked(transactions); } @@ -6647,17 +6624,6 @@ perfetto::protos::LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t tra } } - if (mLegacyFrontEndEnabled) { - perfetto::protos::LayersProto layersProto; - for (const sp& layer : mDrawingState.layersSortedByZ) { - if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) { - continue; - } - layer->writeToProto(layersProto, traceFlags); - } - return layersProto; - } - return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos, mLegacyLayers, traceFlags) .generate(mLayerHierarchyBuilder.getHierarchy()); @@ -6907,10 +6873,6 @@ void SurfaceFlinger::dumpAll(const DumpArgs& args, const std::string& compositio } result.push_back('\n'); - if (mLegacyFrontEndEnabled) { - dumpHwcLayersMinidumpLockedLegacy(result); - } - { DumpArgs plannerArgs; plannerArgs.add(); // first argument is ignored @@ -9236,7 +9198,7 @@ void SurfaceFlinger::moveSnapshotsFromCompositionArgs( snapshots[i] = std::move(layerFE->mSnapshot); } } - if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + if (!mLayerLifecycleManagerEnabled) { for (auto [layer, layerFE] : layers) { layer->updateLayerSnapshot(std::move(layerFE->mSnapshot)); } @@ -9273,7 +9235,7 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit layers.emplace_back(legacyLayer.get(), layerFE.get()); }); } - if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) { + if (!mLayerLifecycleManagerEnabled) { auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) { if (const auto& layerFE = layer->getCompositionEngineLayerFE()) { if (cursorOnly && diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 44fa80607d..e04bf172df 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1486,7 +1486,6 @@ private: bool mPowerHintSessionEnabled; bool mLayerLifecycleManagerEnabled = false; - bool mLegacyFrontEndEnabled = true; frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext); frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp index f127213f0d..5852b1c45f 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp @@ -28,6 +28,7 @@ namespace android { class ColorMatrixTest : public CommitAndCompositeTest {}; TEST_F(ColorMatrixTest, colorMatrixChanged) { + mFlinger.enableLayerLifecycleManager(); EXPECT_COLOR_MATRIX_CHANGED(true, true); mFlinger.mutableTransactionFlags() |= eTransactionNeeded; @@ -45,6 +46,7 @@ TEST_F(ColorMatrixTest, colorMatrixChanged) { } TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) { + mFlinger.enableLayerLifecycleManager(); EXPECT_COLOR_MATRIX_CHANGED(true, true); mFlinger.mutableTransactionFlags() |= eTransactionNeeded; diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 85b17176fa..d40b2f8a49 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -691,6 +691,10 @@ public: return mFlinger->initTransactionTraceWriter(); } + // Needed since mLayerLifecycleManagerEnabled is false by default and must + // be enabled for tests to go through the new front end path. + void enableLayerLifecycleManager() { mFlinger->mLayerLifecycleManagerEnabled = true; } + void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod, TimePoint expectedPresentTime, Fps frameInterval, std::optional timeoutOpt) { -- GitLab From 490ccc99d384e0901bfc0d26300738d086abd4c1 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Tue, 30 Apr 2024 14:26:21 +0000 Subject: [PATCH 297/465] Rename InputConfig field SENSITIVE_FOR_TRACING to SENSITIVE_FOR_PRIVACY Making this field more generic to cater for wider use cases. In addition to input tracing this field will also be used to hide privacy sensitive input interactions from mirrored displays. Test: presubmit Bug: 325252005 Change-Id: I12c34ed5bfc320976033d2bd040b447654d906c0 --- libs/gui/include/gui/WindowInfo.h | 4 ++-- libs/input/android/os/InputConfig.aidl | 6 +++-- .../dispatcher/trace/InputTracer.cpp | 2 +- services/inputflinger/tests/FakeWindows.h | 2 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 4 ++-- .../tests/unittests/LayerSnapshotTest.cpp | 22 +++++++++---------- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index e4f1890c76..b73e497032 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -178,8 +178,8 @@ struct WindowInfo : public Parcelable { static_cast(os::InputConfig::CLONE), GLOBAL_STYLUS_BLOCKS_TOUCH = static_cast(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH), - SENSITIVE_FOR_TRACING = - static_cast(os::InputConfig::SENSITIVE_FOR_TRACING), + SENSITIVE_FOR_PRIVACY = + static_cast(os::InputConfig::SENSITIVE_FOR_PRIVACY), // clang-format on }; diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl index 6b97cbbc59..da62e03821 100644 --- a/libs/input/android/os/InputConfig.aidl +++ b/libs/input/android/os/InputConfig.aidl @@ -159,10 +159,12 @@ enum InputConfig { GLOBAL_STYLUS_BLOCKS_TOUCH = 1 << 17, /** - * InputConfig used to indicate that this window is sensitive for tracing. + * InputConfig used to indicate that this window is privacy sensitive. This may be used to + * redact input interactions from tracing or screen mirroring. + * * This must be set on windows that use {@link WindowManager.LayoutParams#FLAG_SECURE}, * but it may also be set without setting FLAG_SECURE. The tracing configuration will * determine how these sensitive events are eventually traced. */ - SENSITIVE_FOR_TRACING = 1 << 18, + SENSITIVE_FOR_PRIVACY = 1 << 18, } diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index 4931a5f5dd..a1a87afd04 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -88,7 +88,7 @@ InputTargetInfo getTargetInfo(const InputTarget& target) { } const auto& info = *target.windowHandle->getInfo(); const bool isSensitiveTarget = - info.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING); + info.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY); return {target.windowHandle->getInfo()->ownerUid, isSensitiveTarget}; } diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index 26c2b4b1e7..6cd76b229d 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -164,7 +164,7 @@ public: using namespace ftl::flag_operators; mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE; } - mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_TRACING, secure); + mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_PRIVACY, secure); } inline void setInterceptsStylus(bool interceptsStylus) { diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index a2b53297ab..2ff0facdba 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -1061,8 +1061,8 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, } if (snapshot.isSecure || - parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_TRACING)) { - snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_TRACING; + parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_PRIVACY)) { + snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_PRIVACY; } updateVisibility(snapshot, snapshot.isVisible); diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 7c6cff0576..82adadc368 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -1204,34 +1204,34 @@ TEST_F(LayerSnapshotTest, setSensitiveForTracingConfigForSecureLayers) { UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); } TEST_F(LayerSnapshotTest, setSensitiveForTracingFromInputWindowHandle) { setInputInfo(11, [](auto& inputInfo) { - inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING; + inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; }); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test( - gui::WindowInfo::InputConfig::SENSITIVE_FOR_TRACING)); + gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)); } // b/314350323 -- GitLab From 6e46515ab06dd59161f10f6fc736e54a8ec23f3e Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 28 Sep 2022 11:00:25 -0400 Subject: [PATCH 298/465] SF: Introduce DisplayModeController DisplayModeController will centralize the logic and state for selecting the DisplayMode of each physical display. For now, register a display's DisplaySnapshot with DMC during configure, and move the creation of its RefreshRateSelector to DMC::registerDisplay. Bug: 241285876 Test: presubmit Change-Id: I673914328bea64636b8bcd193e710131926334a5 --- services/surfaceflinger/Android.bp | 1 + .../Display/DisplayModeController.cpp | 49 ++++++++++ .../Display/DisplayModeController.h | 73 +++++++++++++++ .../Display/DisplaySnapshotRef.h | 27 ++++++ .../surfaceflinger/Display/PhysicalDisplay.h | 4 +- services/surfaceflinger/DisplayDevice.h | 1 - services/surfaceflinger/SurfaceFlinger.cpp | 91 ++++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 16 ++-- .../tests/unittests/CommitAndCompositeTest.h | 2 +- .../tests/unittests/CompositionTest.cpp | 2 +- ...nger_SetupNewDisplayDeviceInternalTest.cpp | 13 ++- .../tests/unittests/TestableSurfaceFlinger.h | 87 +++++++++--------- 12 files changed, 266 insertions(+), 100 deletions(-) create mode 100644 services/surfaceflinger/Display/DisplayModeController.cpp create mode 100644 services/surfaceflinger/Display/DisplayModeController.h create mode 100644 services/surfaceflinger/Display/DisplaySnapshotRef.h diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index dc69b819c8..8ca796e93d 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -158,6 +158,7 @@ filegroup { "BackgroundExecutor.cpp", "Client.cpp", "ClientCache.cpp", + "Display/DisplayModeController.cpp", "Display/DisplaySnapshot.cpp", "DisplayDevice.cpp", "DisplayHardware/AidlComposerHal.cpp", diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp new file mode 100644 index 0000000000..f093384921 --- /dev/null +++ b/services/surfaceflinger/Display/DisplayModeController.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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. + */ + +#undef LOG_TAG +#define LOG_TAG "DisplayModeController" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + +#include "Display/DisplayModeController.h" +#include "Display/DisplaySnapshot.h" + +#include + +namespace android::display { + +void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef, + DisplayModeId activeModeId, + scheduler::RefreshRateSelector::Config config) { + const auto& snapshot = snapshotRef.get(); + const auto displayId = snapshot.displayId(); + + mDisplays.emplace_or_replace(displayId, snapshotRef, snapshot.displayModes(), activeModeId, + config); +} + +void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) { + const bool ok = mDisplays.erase(displayId); + ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str()); +} + +auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) -> RefreshRateSelectorPtr { + return mDisplays.get(displayId) + .transform([](const Display& display) { return display.selectorPtr; }) + .value_or(nullptr); +} + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h new file mode 100644 index 0000000000..b6a6bee714 --- /dev/null +++ b/services/surfaceflinger/Display/DisplayModeController.h @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include "Display/DisplaySnapshotRef.h" +#include "DisplayHardware/DisplayMode.h" +#include "Scheduler/RefreshRateSelector.h" +#include "ThreadContext.h" + +namespace android::display { + +// Selects the DisplayMode of each physical display, in accordance with DisplayManager policy and +// certain heuristic signals. +class DisplayModeController { +public: + // The referenced DisplaySnapshot must outlive the registration. + void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config) + REQUIRES(kMainThreadContext); + void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext); + + // TODO(b/241285876): Remove once ownership is no longer shared with DisplayDevice. + using RefreshRateSelectorPtr = std::shared_ptr; + + // Returns `nullptr` if the display is no longer registered (or never was). + RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) REQUIRES(kMainThreadContext); + + // Used by tests to inject an existing RefreshRateSelector. + // TODO(b/241285876): Remove this. + void registerDisplay(PhysicalDisplayId displayId, DisplaySnapshotRef snapshotRef, + RefreshRateSelectorPtr selectorPtr) { + mDisplays.emplace_or_replace(displayId, snapshotRef, selectorPtr); + } + +private: + struct Display { + Display(DisplaySnapshotRef snapshot, RefreshRateSelectorPtr selectorPtr) + : snapshot(snapshot), selectorPtr(std::move(selectorPtr)) {} + + Display(DisplaySnapshotRef snapshot, DisplayModes modes, DisplayModeId activeModeId, + scheduler::RefreshRateSelector::Config config) + : Display(snapshot, + std::make_shared(std::move(modes), + activeModeId, config)) {} + + const DisplaySnapshotRef snapshot; + const RefreshRateSelectorPtr selectorPtr; + }; + + ui::PhysicalDisplayMap mDisplays; +}; + +} // namespace android::display diff --git a/services/surfaceflinger/Display/DisplaySnapshotRef.h b/services/surfaceflinger/Display/DisplaySnapshotRef.h new file mode 100644 index 0000000000..6cc5f7e807 --- /dev/null +++ b/services/surfaceflinger/Display/DisplaySnapshotRef.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace android::display { + +class DisplaySnapshot; + +using DisplaySnapshotRef = std::reference_wrapper; + +} // namespace android::display diff --git a/services/surfaceflinger/Display/PhysicalDisplay.h b/services/surfaceflinger/Display/PhysicalDisplay.h index ef36234942..9b1f1ed921 100644 --- a/services/surfaceflinger/Display/PhysicalDisplay.h +++ b/services/surfaceflinger/Display/PhysicalDisplay.h @@ -25,6 +25,7 @@ #include #include "DisplaySnapshot.h" +#include "DisplaySnapshotRef.h" namespace android::display { @@ -45,8 +46,7 @@ public: // Transformers for PhysicalDisplays::get. - using SnapshotRef = std::reference_wrapper; - SnapshotRef snapshotRef() const { return std::cref(mSnapshot); } + DisplaySnapshotRef snapshotRef() const { return std::cref(mSnapshot); } bool isInternal() const { return mSnapshot.connectionType() == ui::DisplayConnectionType::Internal; diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index edd57cce91..fc5089b6d3 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -363,7 +363,6 @@ struct DisplayDeviceCreationArgs { hardware::graphics::composer::hal::PowerMode initialPowerMode{ hardware::graphics::composer::hal::PowerMode::OFF}; bool isPrimary{false}; - DisplayModeId activeModeId; // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only Fps requestedRefreshRate; }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f80a6b61ea..b3ba0e90f9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -228,7 +228,7 @@ bool validateCompositionDataspace(Dataspace dataspace) { return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3; } -std::chrono::milliseconds getIdleTimerTimeout(DisplayId displayId) { +std::chrono::milliseconds getIdleTimerTimeout(PhysicalDisplayId displayId) { if (const int32_t displayIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms_"s + std::to_string(displayId.value), @@ -242,7 +242,7 @@ std::chrono::milliseconds getIdleTimerTimeout(DisplayId displayId) { return std::chrono::milliseconds(millis); } -bool getKernelIdleTimerSyspropConfig(DisplayId displayId) { +bool getKernelIdleTimerSyspropConfig(PhysicalDisplayId displayId) { const bool displaySupportKernelIdleTimer = base::GetBoolProperty("debug.sf.support_kernel_idle_timer_"s + std::to_string(displayId.value), @@ -3520,15 +3520,40 @@ bool SurfaceFlinger::configureLocked() { ')'); if (connection == hal::Connection::CONNECTED) { - if (!processHotplugConnect(displayId, hwcDisplayId, std::move(*info), - displayString.c_str())) { + const auto activeModeIdOpt = + processHotplugConnect(displayId, hwcDisplayId, std::move(*info), + displayString.c_str()); + if (!activeModeIdOpt) { if (FlagManager::getInstance().hotplug2()) { mScheduler->dispatchHotplugError( static_cast(DisplayHotplugEvent::ERROR_UNKNOWN)); } getHwComposer().disconnectDisplay(displayId); + continue; } + + const auto [kernelIdleTimerController, idleTimerTimeoutMs] = + getKernelIdleTimerProperties(displayId); + + using Config = scheduler::RefreshRateSelector::Config; + const Config config = + {.enableFrameRateOverride = sysprop::enable_frame_rate_override(true) + ? Config::FrameRateOverride::Enabled + : Config::FrameRateOverride::Disabled, + .frameRateMultipleThreshold = + base::GetIntProperty("debug.sf.frame_rate_multiple_threshold"s, 0), + .legacyIdleTimerTimeout = idleTimerTimeoutMs, + .kernelIdleTimerController = kernelIdleTimerController}; + + const auto snapshotOpt = + mPhysicalDisplays.get(displayId).transform(&PhysicalDisplay::snapshotRef); + LOG_ALWAYS_FATAL_IF(!snapshotOpt); + + mDisplayModeController.registerDisplay(*snapshotOpt, *activeModeIdOpt, config); } else { + // Unregister before destroying the DisplaySnapshot below. + mDisplayModeController.unregisterDisplay(displayId); + processHotplugDisconnect(displayId, displayString.c_str()); } } @@ -3537,16 +3562,17 @@ bool SurfaceFlinger::configureLocked() { return !events.empty(); } -bool SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId, - hal::HWDisplayId hwcDisplayId, - DisplayIdentificationInfo&& info, - const char* displayString) { +std::optional SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId, + hal::HWDisplayId hwcDisplayId, + DisplayIdentificationInfo&& info, + const char* displayString) { auto [displayModes, activeMode] = loadDisplayModes(displayId); if (!activeMode) { ALOGE("Failed to hotplug %s", displayString); - return false; + return std::nullopt; } + const DisplayModeId activeModeId = activeMode->getId(); ui::ColorModes colorModes = getHwComposer().getColorModes(displayId); if (const auto displayOpt = mPhysicalDisplays.get(displayId)) { @@ -3569,7 +3595,7 @@ bool SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId, state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId. state.physical->activeMode = std::move(activeMode); ALOGI("Reconnecting %s", displayString); - return true; + return activeModeId; } const sp token = sp::make(); @@ -3590,7 +3616,7 @@ bool SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId, mCurrentState.displays.add(token, state); ALOGI("Connecting %s", displayString); - return true; + return activeModeId; } void SurfaceFlinger::processHotplugDisconnect(PhysicalDisplayId displayId, @@ -3633,43 +3659,21 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( creationArgs.hasWideColorGamut = false; creationArgs.supportedPerFrameMetadata = 0; - if (const auto& physical = state.physical) { - creationArgs.activeModeId = physical->activeMode->getId(); - const auto [kernelIdleTimerController, idleTimerTimeoutMs] = - getKernelIdleTimerProperties(compositionDisplay->getId()); - - using Config = scheduler::RefreshRateSelector::Config; - const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true) - ? Config::FrameRateOverride::Enabled - : Config::FrameRateOverride::Disabled; - const Config config = - {.enableFrameRateOverride = enableFrameRateOverride, - .frameRateMultipleThreshold = - base::GetIntProperty("debug.sf.frame_rate_multiple_threshold"s, 0), - .legacyIdleTimerTimeout = idleTimerTimeoutMs, - .kernelIdleTimerController = kernelIdleTimerController}; + if (const auto physicalIdOpt = PhysicalDisplayId::tryCast(compositionDisplay->getId())) { + const auto physicalId = *physicalIdOpt; + creationArgs.isPrimary = physicalId == getPrimaryDisplayIdLocked(); creationArgs.refreshRateSelector = - mPhysicalDisplays.get(physical->id) - .transform(&PhysicalDisplay::snapshotRef) - .transform([&](const display::DisplaySnapshot& snapshot) { - return std::make_shared< - scheduler::RefreshRateSelector>(snapshot.displayModes(), - creationArgs.activeModeId, - config); - }) - .value_or(nullptr); - - creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked(); - - mPhysicalDisplays.get(physical->id) + FTL_FAKE_GUARD(kMainThreadContext, + mDisplayModeController.selectorPtrFor(physicalId)); + + mPhysicalDisplays.get(physicalId) .transform(&PhysicalDisplay::snapshotRef) .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) { for (const auto mode : snapshot.colorModes()) { creationArgs.hasWideColorGamut |= ui::isWideColorMode(mode); creationArgs.hwcColorModes - .emplace(mode, - getHwComposer().getRenderIntents(physical->id, mode)); + .emplace(mode, getHwComposer().getRenderIntents(physicalId, mode)); } })); } @@ -7672,13 +7676,12 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { } std::pair, std::chrono::milliseconds> -SurfaceFlinger::getKernelIdleTimerProperties(DisplayId displayId) { +SurfaceFlinger::getKernelIdleTimerProperties(PhysicalDisplayId displayId) { const bool isKernelIdleTimerHwcSupported = getHwComposer().getComposer()->isSupported( android::Hwc2::Composer::OptionalFeature::KernelIdleTimer); const auto timeout = getIdleTimerTimeout(displayId); if (isKernelIdleTimerHwcSupported) { - if (const auto id = PhysicalDisplayId::tryCast(displayId); - getHwComposer().hasDisplayIdleTimerCapability(*id)) { + if (getHwComposer().hasDisplayIdleTimerCapability(displayId)) { // In order to decide if we can use the HWC api for idle timer // we query DisplayCapability::DISPLAY_IDLE_TIMER directly on the composer // without relying on hasDisplayCapability. diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 83b092d831..6211e2d5a9 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -65,6 +65,7 @@ #include #include +#include "Display/DisplayModeController.h" #include "Display/PhysicalDisplay.h" #include "DisplayDevice.h" #include "DisplayHardware/HWC2.h" @@ -707,7 +708,7 @@ private: // Get the controller and timeout that will help decide how the kernel idle timer will be // configured and what value to use as the timeout. std::pair, std::chrono::milliseconds> - getKernelIdleTimerProperties(DisplayId) REQUIRES(mStateLock); + getKernelIdleTimerProperties(PhysicalDisplayId) REQUIRES(mStateLock); // Updates the kernel idle timer either through HWC or through sysprop // depending on which controller is provided void updateKernelIdleTimer(std::chrono::milliseconds timeoutMs, KernelIdleTimerController, @@ -989,8 +990,7 @@ private: return getDefaultDisplayDeviceLocked(); } - using DisplayDeviceAndSnapshot = - std::pair, display::PhysicalDisplay::SnapshotRef>; + using DisplayDeviceAndSnapshot = std::pair, display::DisplaySnapshotRef>; // Combinator for ftl::Optional::and_then. auto getDisplayDeviceAndSnapshot() REQUIRES(mStateLock) { @@ -1059,9 +1059,11 @@ private: bool configureLocked() REQUIRES(mStateLock) REQUIRES(kMainThreadContext) EXCLUDES(mHotplugMutex); - // Returns false on hotplug failure. - bool processHotplugConnect(PhysicalDisplayId, hal::HWDisplayId, DisplayIdentificationInfo&&, - const char* displayString) REQUIRES(mStateLock, kMainThreadContext); + // Returns the active mode ID, or nullopt on hotplug failure. + std::optional processHotplugConnect(PhysicalDisplayId, hal::HWDisplayId, + DisplayIdentificationInfo&&, + const char* displayString) + REQUIRES(mStateLock, kMainThreadContext); void processHotplugDisconnect(PhysicalDisplayId, const char* displayString) REQUIRES(mStateLock, kMainThreadContext); @@ -1324,6 +1326,8 @@ private: // reads from ISchedulerCallback::requestDisplayModes may happen concurrently. std::atomic mActiveDisplayId GUARDED_BY(mStateLock); + display::DisplayModeController mDisplayModeController; + struct { DisplayIdGenerator gpu; std::optional> hal; diff --git a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h index 34e4ba5e78..d4c801f050 100644 --- a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h +++ b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h @@ -60,7 +60,7 @@ struct CommitAndCompositeTest : testing::Test { .setNativeWindow(mNativeWindow) .setPowerMode(hal::PowerMode::ON) .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector()) - .skipRegisterDisplay() + .skipSchedulerRegistration() .inject(); } diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 7d8a30a727..0ddddbd7f3 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -282,7 +282,7 @@ struct BaseDisplayVariant { .setSecure(Derived::IS_SECURE) .setPowerMode(Derived::INIT_POWER_MODE) .setRefreshRateSelector(test->mFlinger.scheduler()->refreshRateSelector()) - .skipRegisterDisplay() + .skipSchedulerRegistration() .inject(); Mock::VerifyAndClear(test->mNativeWindow.get()); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp index c0796df6cb..1bae5ffd30 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp @@ -255,10 +255,15 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { colorModes.push_back(ColorMode::DISPLAY_P3); } - mFlinger.mutablePhysicalDisplays().emplace_or_replace(*displayId, displayToken, *displayId, - *connectionType, - makeModes(activeMode), - std::move(colorModes), std::nullopt); + const auto it = mFlinger.mutablePhysicalDisplays() + .emplace_or_replace(*displayId, displayToken, *displayId, + *connectionType, makeModes(activeMode), + std::move(colorModes), std::nullopt) + .first; + + FTL_FAKE_GUARD(kMainThreadContext, + mFlinger.mutableDisplayModeController() + .registerDisplay(it->second.snapshot(), activeMode->getId(), {})); } state.isSecure = static_cast(Case::Display::SECURE); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 85b17176fa..72ea6ed7e5 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -657,6 +657,7 @@ public: auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; } + auto& mutableDisplayModeController() { return mFlinger->mDisplayModeController; } auto& mutableCurrentState() { return mFlinger->mCurrentState; } auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; } auto& mutableDisplays() { return mFlinger->mDisplays; } @@ -967,14 +968,14 @@ public: auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) { mDisplayModes = std::move(modes); - mCreationArgs.activeModeId = activeModeId; + mActiveModeId = activeModeId; mCreationArgs.refreshRateSelector = nullptr; return *this; } auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) { mDisplayModes = selectorPtr->displayModes(); - mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId(); + mActiveModeId = selectorPtr->getActiveMode().modePtr->getId(); mCreationArgs.refreshRateSelector = std::move(selectorPtr); return *this; } @@ -1016,8 +1017,9 @@ public: return *this; } - auto& skipRegisterDisplay() { - mRegisterDisplay = false; + // Used to avoid overwriting mocks injected by TestableSurfaceFlinger::setupMockScheduler. + auto& skipSchedulerRegistration() { + mSchedulerRegistration = false; return *this; } @@ -1030,12 +1032,24 @@ public: std::shared_ptr tracker) NO_THREAD_SAFETY_ANALYSIS { const auto displayId = mCreationArgs.compositionDisplay->getDisplayId(); + LOG_ALWAYS_FATAL_IF(!displayId); auto& modes = mDisplayModes; - auto& activeModeId = mCreationArgs.activeModeId; + auto& activeModeId = mActiveModeId; + std::optional refreshRateOpt; - if (displayId && !mCreationArgs.refreshRateSelector) { - if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) { + DisplayDeviceState state; + state.isSecure = mCreationArgs.isSecure; + + if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) { + LOG_ALWAYS_FATAL_IF(!mConnectionType); + LOG_ALWAYS_FATAL_IF(!mHwcDisplayId); + + if (mCreationArgs.isPrimary) { + mFlinger.mutableActiveDisplayId() = *physicalId; + } + + if (!mCreationArgs.refreshRateSelector) { if (modes.empty()) { constexpr DisplayModeId kModeId{0}; DisplayModePtr mode = @@ -1057,48 +1071,38 @@ public: mCreationArgs.refreshRateSelector = std::make_shared(modes, activeModeId); } - } - - sp display = sp::make(mCreationArgs); - mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); - - DisplayDeviceState state; - state.isSecure = mCreationArgs.isSecure; - - if (mConnectionType) { - LOG_ALWAYS_FATAL_IF(!displayId); - const auto physicalIdOpt = PhysicalDisplayId::tryCast(*displayId); - LOG_ALWAYS_FATAL_IF(!physicalIdOpt); - const auto physicalId = *physicalIdOpt; - - if (mCreationArgs.isPrimary) { - mFlinger.mutableActiveDisplayId() = physicalId; - } - LOG_ALWAYS_FATAL_IF(!mHwcDisplayId); + const auto activeModeOpt = modes.get(activeModeId); + LOG_ALWAYS_FATAL_IF(!activeModeOpt); + refreshRateOpt = activeModeOpt->get()->getPeakFps(); - const auto activeMode = modes.get(activeModeId); - LOG_ALWAYS_FATAL_IF(!activeMode); - const auto fps = activeMode->get()->getPeakFps(); - - state.physical = {.id = physicalId, + state.physical = {.id = *physicalId, .hwcDisplayId = *mHwcDisplayId, - .activeMode = activeMode->get()}; + .activeMode = activeModeOpt->get()}; + + const auto it = mFlinger.mutablePhysicalDisplays() + .emplace_or_replace(*physicalId, mDisplayToken, *physicalId, + *mConnectionType, std::move(modes), + ui::ColorModes(), std::nullopt) + .first; - mFlinger.mutablePhysicalDisplays().emplace_or_replace(physicalId, mDisplayToken, - physicalId, *mConnectionType, - std::move(modes), - ui::ColorModes(), - std::nullopt); + mFlinger.mutableDisplayModeController() + .registerDisplay(*physicalId, it->second.snapshot(), + mCreationArgs.refreshRateSelector); - if (mFlinger.scheduler() && mRegisterDisplay) { - mFlinger.scheduler()->registerDisplay(physicalId, - display->holdRefreshRateSelector(), + if (mFlinger.scheduler() && mSchedulerRegistration) { + mFlinger.scheduler()->registerDisplay(*physicalId, + mCreationArgs.refreshRateSelector, std::move(controller), std::move(tracker)); } + } + + sp display = sp::make(mCreationArgs); + mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); - display->setActiveMode(activeModeId, fps, fps); + if (refreshRateOpt) { + display->setActiveMode(activeModeId, *refreshRateOpt, *refreshRateOpt); } mFlinger.mutableCurrentState().displays.add(mDisplayToken, state); @@ -1112,7 +1116,8 @@ public: sp mDisplayToken = sp::make(); DisplayDeviceCreationArgs mCreationArgs; DisplayModes mDisplayModes; - bool mRegisterDisplay = true; + DisplayModeId mActiveModeId; + bool mSchedulerRegistration = true; const std::optional mConnectionType; const std::optional mHwcDisplayId; }; -- GitLab From 1ec2d9a02a3c4e6baf9628ad4bfa90acbd4c981c Mon Sep 17 00:00:00 2001 From: Isaac Chai Date: Thu, 11 Apr 2024 20:10:34 +0000 Subject: [PATCH 299/465] Add color correction level API to native Test: Locally tested with other cls of the same topic, and also added unit tests and ran 'atest SurfaceFlinger_test' Bug: 322829049 Change-Id: I494ce39608ddf84fb74b60291511477672ca4e36 --- .../surfaceflinger/Effects/Daltonizer.cpp | 39 ++++-- services/surfaceflinger/Effects/Daltonizer.h | 10 ++ services/surfaceflinger/SurfaceFlinger.cpp | 1 + .../surfaceflinger/tests/unittests/Android.bp | 1 + .../tests/unittests/DaltonizerTest.cpp | 126 ++++++++++++++++++ 5 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 services/surfaceflinger/tests/unittests/DaltonizerTest.cpp diff --git a/services/surfaceflinger/Effects/Daltonizer.cpp b/services/surfaceflinger/Effects/Daltonizer.cpp index a7090c51f2..65f2605a1e 100644 --- a/services/surfaceflinger/Effects/Daltonizer.cpp +++ b/services/surfaceflinger/Effects/Daltonizer.cpp @@ -37,6 +37,18 @@ void Daltonizer::setMode(ColorBlindnessMode mode) { } } +void Daltonizer::setLevel(int32_t level) { + if (level < 0 || level > 10) { + return; + } + + float newLevel = level / 10.0f; + if (std::fabs(mLevel - newLevel) > 0.09f) { + mDirty = true; + } + mLevel = newLevel; +} + const mat4& Daltonizer::operator()() { if (mDirty) { mDirty = false; @@ -117,25 +129,24 @@ void Daltonizer::update() { // a color blind user and "spread" this error onto the healthy cones. // The matrices below perform this last step and have been chosen arbitrarily. - // The amount of correction can be adjusted here. - + // Scale 0 represents no change (mColorTransform is identical matrix). // error spread for protanopia - const mat4 errp( 1.0, 0.7, 0.7, 0, - 0.0, 1.0, 0.0, 0, - 0.0, 0.0, 1.0, 0, - 0, 0, 0, 1); + const mat4 errp(1.0, mLevel, mLevel, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); // error spread for deuteranopia - const mat4 errd( 1.0, 0.0, 0.0, 0, - 0.7, 1.0, 0.7, 0, - 0.0, 0.0, 1.0, 0, - 0, 0, 0, 1); + const mat4 errd( 1.0, 0.0, 0.0, 0.0, + mLevel, 1.0, mLevel, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); // error spread for tritanopia - const mat4 errt( 1.0, 0.0, 0.0, 0, - 0.0, 1.0, 0.0, 0, - 0.7, 0.7, 1.0, 0, - 0, 0, 0, 1); + const mat4 errt( 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + mLevel, mLevel, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); // And the magic happens here... // We construct the matrix that will perform the whole correction. diff --git a/services/surfaceflinger/Effects/Daltonizer.h b/services/surfaceflinger/Effects/Daltonizer.h index 2fb60e96d2..f5eaae7ab4 100644 --- a/services/surfaceflinger/Effects/Daltonizer.h +++ b/services/surfaceflinger/Effects/Daltonizer.h @@ -21,6 +21,9 @@ namespace android { +// Forward declare test class +class DaltonizerTest; + enum class ColorBlindnessType { None, // Disables the Daltonizer Protanomaly, // L (red) cone deficient @@ -37,10 +40,15 @@ class Daltonizer { public: void setType(ColorBlindnessType type); void setMode(ColorBlindnessMode mode); + // sets level for correction saturation, [0-10]. + void setLevel(int32_t level); // returns the color transform to apply in the shader const mat4& operator()(); + // For testing. + friend class DaltonizerTest; + private: void update(); @@ -48,6 +56,8 @@ private: ColorBlindnessMode mMode = ColorBlindnessMode::Simulation; bool mDirty = true; mat4 mColorTransform; + // level of error spreading, [0.0-1.0]. + float mLevel = 0.7f; }; } /* namespace android */ diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c237a7da3e..70b9e4361d 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7154,6 +7154,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r Mutex::Autolock _l(mStateLock); // daltonize n = data.readInt32(); + mDaltonizer.setLevel(data.readInt32()); switch (n % 10) { case 1: mDaltonizer.setType(ColorBlindnessType::Protanomaly); diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 0c13db3d94..5145e112d6 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -66,6 +66,7 @@ cc_test { "BackgroundExecutorTest.cpp", "CommitTest.cpp", "CompositionTest.cpp", + "DaltonizerTest.cpp", "DisplayIdGeneratorTest.cpp", "DisplayTransactionTest.cpp", "DisplayDevice_GetBestColorModeTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp b/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp new file mode 100644 index 0000000000..9f632a1430 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2018 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. + */ + +#include +#include +#include +#include "Effects/Daltonizer.h" + +namespace android { + +class DaltonizerTest { +private: + Daltonizer& mDaltonizer; + +public: + DaltonizerTest(Daltonizer& daltonizer) : mDaltonizer(daltonizer) {} + + bool isDirty() const { return mDaltonizer.mDirty; } + + float getLevel() const { return mDaltonizer.mLevel; } + + ColorBlindnessType getType() const { return mDaltonizer.mType; } +}; + +constexpr float TOLERANCE = 0.01f; + +static bool isIdentityMatrix(mat4& matrix) { + for (size_t i = 0; i < 4; ++i) { + for (size_t j = 0; j < 4; ++j) { + if (i == j) { + // Check diagonal elements + if (std::fabs(matrix[i][j] - 1.0f) > TOLERANCE) { + return false; + } + } else { + // Check off-diagonal elements + if (std::fabs(matrix[i][j]) > TOLERANCE) { + return false; + } + } + } + } + return true; +} + +// Test Suite Name : DaltonizerTest, Test name: ConstructionDefaultValues +TEST(DaltonizerTest, ConstructionDefaultValues) { + Daltonizer daltonizer; + DaltonizerTest test(daltonizer); + + EXPECT_EQ(test.getLevel(), 0.7f); + ASSERT_TRUE(test.isDirty()); + EXPECT_EQ(test.getType(), ColorBlindnessType::None); + mat4 matrix = daltonizer(); + ASSERT_TRUE(isIdentityMatrix(matrix)); +} + +TEST(DaltonizerTest, NotDirtyAfterColorMatrixReturned) { + Daltonizer daltonizer; + + mat4 matrix = daltonizer(); + DaltonizerTest test(daltonizer); + + ASSERT_FALSE(test.isDirty()); + ASSERT_TRUE(isIdentityMatrix(matrix)); +} + +TEST(DaltonizerTest, LevelOutOfRangeTooLowIgnored) { + Daltonizer daltonizer; + // Get matrix to reset isDirty == false. + mat4 matrix = daltonizer(); + + daltonizer.setLevel(-1); + DaltonizerTest test(daltonizer); + + EXPECT_EQ(test.getLevel(), 0.7f); + ASSERT_FALSE(test.isDirty()); +} + +TEST(DaltonizerTest, LevelOutOfRangeTooHighIgnored) { + Daltonizer daltonizer; + // Get matrix to reset isDirty == false. + mat4 matrix = daltonizer(); + + daltonizer.setLevel(11); + DaltonizerTest test(daltonizer); + + EXPECT_EQ(test.getLevel(), 0.7f); + ASSERT_FALSE(test.isDirty()); +} + +TEST(DaltonizerTest, ColorCorrectionMatrixNonIdentical) { + Daltonizer daltonizer; + daltonizer.setType(ColorBlindnessType::Protanomaly); + daltonizer.setMode(ColorBlindnessMode::Correction); + + mat4 matrix = daltonizer(); + + ASSERT_FALSE(isIdentityMatrix(matrix)); +} + +TEST(DaltonizerTest, LevelZeroColorMatrixEqIdentityMatrix) { + Daltonizer daltonizer; + daltonizer.setType(ColorBlindnessType::Protanomaly); + daltonizer.setMode(ColorBlindnessMode::Correction); + daltonizer.setLevel(0); + + mat4 matrix = daltonizer(); + + ASSERT_TRUE(isIdentityMatrix(matrix)); +} + +} /* namespace android */ -- GitLab From 22242fb811ef4ff2dd95f1107f07aa18ef1e098f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 3 May 2024 19:58:22 +0000 Subject: [PATCH 300/465] Allow a message argument for __DEPRECATED_IN. The messages come from a different CL in AOSP, but that CL breaks internally because this code doesn't exist in AOSP. Merging the compatibility thing first so I can land the rest in AOSP. Bug: None Test: None Change-Id: I906c6ea7bd6d891aba9e185756131ac5b34f3c3d --- include/android/choreographer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/android/choreographer.h b/include/android/choreographer.h index d4f30efe3a..8927d2c012 100644 --- a/include/android/choreographer.h +++ b/include/android/choreographer.h @@ -58,7 +58,7 @@ #define __INTRODUCED_IN(__api_level) /* nothing */ #endif #if !defined(__DEPRECATED_IN) -#define __DEPRECATED_IN(__api_level) __attribute__((__deprecated__)) +#define __DEPRECATED_IN(__api_level, ...) __attribute__((__deprecated__)) #endif __BEGIN_DECLS -- GitLab From 8b053510e6a7e094d2ee993fca75ec868f2e7e9f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 3 May 2024 23:15:39 +0000 Subject: [PATCH 301/465] Touchpad: Cleanup after PointerChoreographer refactor Remove duplicated tests and references to PointerController. Bug: 311416205 Test: atest inputflinger_tests Change-Id: I02bd7c8f4c4126c9ae7d2fdffd94175b37478925 --- .../reader/mapper/TouchpadInputMapper.cpp | 45 +- .../reader/mapper/TouchpadInputMapper.h | 7 - .../mapper/gestures/GestureConverter.cpp | 168 +- .../reader/mapper/gestures/GestureConverter.h | 17 +- .../tests/GestureConverter_test.cpp | 1568 +---------------- .../tests/TouchpadInputMapper_test.cpp | 79 +- 6 files changed, 121 insertions(+), 1763 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index f558ba1196..6a38aa7b08 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -47,8 +47,6 @@ namespace android { namespace { -static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer(); - /** * Log details of each gesture output by the gestures library. * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires @@ -237,20 +235,13 @@ private: TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) - : TouchpadInputMapper(deviceContext, readerConfig, ENABLE_POINTER_CHOREOGRAPHER) {} - -TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig, - bool enablePointerChoreographer) : InputMapper(deviceContext, readerConfig), mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter), - mPointerController(getContext()->getPointerController(getDeviceId())), mTimerProvider(*getContext()), mStateConverter(deviceContext, mMotionAccumulator), mGestureConverter(*getContext(), deviceContext, getDeviceId()), mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()), - mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())), - mEnablePointerChoreographer(enablePointerChoreographer) { + mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) { RawAbsoluteAxisInfo slotAxisInfo; deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { @@ -277,10 +268,6 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, } TouchpadInputMapper::~TouchpadInputMapper() { - if (mPointerController != nullptr) { - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); - } - // The gesture interpreter's destructor will try to free its property and timer providers, // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers. // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already @@ -342,33 +329,12 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, // Only generate events for the associated display. mDisplayId = assocViewport->displayId; resolvedViewport = *assocViewport; - if (!mEnablePointerChoreographer) { - const bool mismatchedPointerDisplay = - (assocViewport->displayId != mPointerController->getDisplayId()); - if (mismatchedPointerDisplay) { - ALOGW("Touchpad \"%s\" associated viewport display does not match pointer " - "controller", - mDeviceContext.getName().c_str()); - mDisplayId.reset(); - } - } } else { // The InputDevice is not associated with a viewport, but it controls the mouse pointer. - if (mEnablePointerChoreographer) { - // Always use DISPLAY_ID_NONE for touchpad events. - // PointerChoreographer will make it target the correct the displayId later. - resolvedViewport = - getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); - mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; - } else { - mDisplayId = mPointerController->getDisplayId(); - if (auto v = config.getDisplayViewportById(*mDisplayId); v) { - resolvedViewport = *v; - } - if (auto bounds = mPointerController->getBounds(); bounds) { - boundsInLogicalDisplay = *bounds; - } - } + // Always use DISPLAY_ID_NONE for touchpad events. + // PointerChoreographer will make it target the correct the displayId later. + resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); + mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; } mGestureConverter.setDisplayId(mDisplayId); @@ -422,7 +388,6 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, // The touchpad is being captured, so we need to tidy up any fake fingers etc. that are // still being reported for a gesture in progress. out += reset(when); - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); } else { // We're transitioning from captured to uncaptured. mCapturedEventConverter.reset(); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 9f272cf846..9f685ec6a8 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -72,10 +72,6 @@ private: void resetGestureInterpreter(nsecs_t when); explicit TouchpadInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig); - // Constructor for testing. - explicit TouchpadInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig, - bool enablePointerChoreographer); void updatePalmDetectionMetrics(); [[nodiscard]] std::list sendHardwareState(nsecs_t when, nsecs_t readTime, SelfContainedHardwareState schs); @@ -83,7 +79,6 @@ private: std::unique_ptr mGestureInterpreter; - std::shared_ptr mPointerController; PropertyProvider mPropertyProvider; TimerProvider mTimerProvider; @@ -111,8 +106,6 @@ private: // Tracking IDs for touches that have at some point been reported as palms by the touchpad. std::set mPalmTrackingIds; - const bool mEnablePointerChoreographer; - // The display that events generated by this mapper should target. This can be set to // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index 39a88e5570..ff95857e96 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -66,7 +66,6 @@ GestureConverter::GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, int32_t deviceId) : mDeviceId(deviceId), mReaderContext(readerContext), - mPointerController(readerContext.getPointerController(deviceId)), mEnableFlingStop(input_flags::enable_touchpad_fling_stop()) { deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo); deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo); @@ -174,7 +173,6 @@ std::list GestureConverter::handleMove(nsecs_t when, nsecs_t readTim const Gesture& gesture) { float deltaX = gesture.details.move.dx; float deltaY = gesture.details.move.dy; - const auto [oldXCursorPosition, oldYCursorPosition] = mPointerController->getPosition(); if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) { bool wasHoverCancelled = mIsHoverCancelled; // Gesture will be cancelled if it started before the user started typing and @@ -185,8 +183,7 @@ std::list GestureConverter::handleMove(nsecs_t when, nsecs_t readTim if (!wasHoverCancelled && mIsHoverCancelled) { // This is the first event of the cancelled gesture, we won't return because we need to // generate a HOVER_EXIT event - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - return exitHover(when, readTime, oldXCursorPosition, oldYCursorPosition); + return exitHover(when, readTime); } else if (mIsHoverCancelled) { return {}; } @@ -202,30 +199,23 @@ std::list GestureConverter::handleMove(nsecs_t when, nsecs_t readTim (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) { enableTapToClick(when); } - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->move(deltaX, deltaY); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); } std::list out; const bool down = isPointerDown(mButtonState); if (!down) { - out += enterHover(when, readTime, oldXCursorPosition, oldYCursorPosition); + out += enterHover(when, readTime); } - const auto [newXCursorPosition, newYCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, newXCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, newYCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE; out.push_back(makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState, - /*pointerCount=*/1, &coords, newXCursorPosition, - newYCursorPosition)); + /*pointerCount=*/1, &coords)); return out; } @@ -233,15 +223,8 @@ std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ const Gesture& gesture) { std::list out = {}; - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); - PointerCoords coords; coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); @@ -274,16 +257,15 @@ std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ newButtonState |= actionButton; pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, newButtonState, - /*pointerCount=*/1, &coords, xCursorPosition, - yCursorPosition)); + /*pointerCount=*/1, &coords)); } } if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) { mDownTime = when; - out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + out += exitHover(when, readTime); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1, - &coords, xCursorPosition, yCursorPosition)); + &coords)); } out.splice(out.end(), pressEvents); @@ -299,16 +281,15 @@ std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ newButtonState &= ~actionButton; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, newButtonState, /* pointerCount= */ 1, - &coords, xCursorPosition, yCursorPosition)); + &coords)); } } if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) { coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - newButtonState, /* pointerCount= */ 1, &coords, - xCursorPosition, yCursorPosition)); + newButtonState, /* pointerCount= */ 1, &coords)); mButtonState = newButtonState; - out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + out += enterHover(when, readTime); } mButtonState = newButtonState; return out; @@ -316,12 +297,9 @@ std::list GestureConverter::handleButtonsChange(nsecs_t when, nsecs_ std::list GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) { std::list out; - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); PointerCoords coords; coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); const bool pointerDown = isPointerDown(mButtonState); @@ -332,17 +310,15 @@ std::list GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t if (mButtonState & button) { newButtonState &= ~button; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE, - button, newButtonState, /*pointerCount=*/1, &coords, - xCursorPosition, yCursorPosition)); + button, newButtonState, /*pointerCount=*/1, &coords)); } } mButtonState = 0; if (pointerDown) { coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, - mButtonState, /*pointerCount=*/1, &coords, xCursorPosition, - yCursorPosition)); - out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + mButtonState, /*pointerCount=*/1, &coords)); + out += enterHover(when, readTime); } return out; } @@ -351,19 +327,15 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT const Gesture& gesture) { std::list out; PointerCoords& coords = mFakeFingerCoords[0]; - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) { - out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + out += exitHover(when, readTime); mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE; - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); mDownTime = when; NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(), - xCursorPosition, yCursorPosition); + mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data()); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); } @@ -378,8 +350,7 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy); NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(), - xCursorPosition, yCursorPosition); + mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data()); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); return out; @@ -409,28 +380,22 @@ std::list GestureConverter::handleFling(nsecs_t when, nsecs_t readTi // avoid side effects (e.g. activation of UI elements). // TODO(b/326056750): add an API for fling stops. mFlingMayBeInProgress = false; - const auto [xCursorPosition, yCursorPosition] = - mPointerController->getPosition(); PointerCoords coords; coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); std::list out; mDownTime = when; mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE; - out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + out += exitHover(when, readTime); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*buttonState=*/0, - /*pointerCount=*/1, &coords, xCursorPosition, - yCursorPosition)); + /*pointerCount=*/1, &coords)); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_CANCEL, /*actionButton=*/0, /*buttonState=*/0, - /*pointerCount=*/1, &coords, xCursorPosition, - yCursorPosition)); - out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + /*pointerCount=*/1, &coords)); + out += enterHover(when, readTime); mCurrentClassification = MotionClassification::NONE; return out; } else { @@ -455,17 +420,15 @@ std::list GestureConverter::handleFling(nsecs_t when, nsecs_t readTi std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) { std::list out; - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0); NotifyMotionArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, - mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(), - xCursorPosition, yCursorPosition); + mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data()); args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE; out.push_back(args); mCurrentClassification = MotionClassification::NONE; - out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + out += enterHover(when, readTime); return out; } @@ -475,26 +438,26 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime float dx, float dy) { std::list out = {}; - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { // If the user changes the number of fingers mid-way through a swipe (e.g. they start with // three and then put a fourth finger down), the gesture library will treat it as two // separate swipes with an appropriate lift event between them, so we don't have to worry // about the finger count changing mid-swipe. - out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + out += exitHover(when, readTime); mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE; mSwipeFingerCount = fingerCount; constexpr float FAKE_FINGER_SPACING = 100; - float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2; + float xCoord = 0.f - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2; for (size_t i = 0; i < mSwipeFingerCount; i++) { PointerCoords& coords = mFakeFingerCoords[i]; coords.clear(); + // PointerChoreographer will add the cursor position to these pointers. coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); xCoord += FAKE_FINGER_SPACING; } @@ -504,14 +467,13 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime fingerCount); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); for (size_t i = 1; i < mSwipeFingerCount; i++) { out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_POINTER_DOWN | (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), /* actionButton= */ 0, mButtonState, - /* pointerCount= */ i + 1, mFakeFingerCoords.data(), - xCursorPosition, yCursorPosition)); + /* pointerCount= */ i + 1, mFakeFingerCoords.data())); } } float rotatedDeltaX = dx, rotatedDeltaY = -dy; @@ -529,7 +491,7 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0, mButtonState, /* pointerCount= */ mSwipeFingerCount, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); return out; } @@ -539,7 +501,6 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) { return out; } - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0); @@ -548,22 +509,20 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime AMOTION_EVENT_ACTION_POINTER_UP | ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), /* actionButton= */ 0, mButtonState, /* pointerCount= */ i, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); } out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0); mCurrentClassification = MotionClassification::NONE; - out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + out += enterHover(when, readTime); mSwipeFingerCount = 0; return out; } [[nodiscard]] std::list GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime, const Gesture& gesture) { - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); - // Pinch gesture phases are reported a little differently from others, in that the same details // struct is used for all phases of the gesture, just with different zoom_state values. When // zoom_state is START or END, dz will always be 1, so we don't need to move the pointers in @@ -575,28 +534,27 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime gesture.details.pinch.zoom_state); std::list out; - out += exitHover(when, readTime, xCursorPosition, yCursorPosition); + out += exitHover(when, readTime); mCurrentClassification = MotionClassification::PINCH; mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX; + // PointerChoreographer will add the cursor position to these pointers. mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0); - mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, - xCursorPosition - mPinchFingerSeparation / 2); - mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f - mPinchFingerSeparation / 2); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); - mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, - xCursorPosition + mPinchFingerSeparation / 2); - mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f + mPinchFingerSeparation / 2); + mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f); mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); mDownTime = when; out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_POINTER_DOWN | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); return out; } @@ -605,77 +563,65 @@ std::list GestureConverter::endScroll(nsecs_t when, nsecs_t readTime } mPinchFingerSeparation *= gesture.details.pinch.dz; + // PointerChoreographer will add the cursor position to these pointers. mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, gesture.details.pinch.dz); - mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, - xCursorPosition - mPinchFingerSeparation / 2); - mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); - mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, - xCursorPosition + mPinchFingerSeparation / 2); - mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f - mPinchFingerSeparation / 2); + mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f); + mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f + mPinchFingerSeparation / 2); + mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f); return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, - mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data(), - xCursorPosition, yCursorPosition)}; + mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data())}; } std::list GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) { std::list out; - const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition(); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_POINTER_UP | 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT, /*actionButton=*/0, mButtonState, /*pointerCount=*/2, - mFakeFingerCoords.data(), xCursorPosition, yCursorPosition)); + mFakeFingerCoords.data())); out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0, - mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data(), - xCursorPosition, yCursorPosition)); + mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data())); mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0); mCurrentClassification = MotionClassification::NONE; - out += enterHover(when, readTime, xCursorPosition, yCursorPosition); + out += enterHover(when, readTime); return out; } -std::list GestureConverter::enterHover(nsecs_t when, nsecs_t readTime, - float xCursorPosition, float yCursorPosition) { +std::list GestureConverter::enterHover(nsecs_t when, nsecs_t readTime) { if (!mIsHovering) { mIsHovering = true; - return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER, xCursorPosition, - yCursorPosition)}; + return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER)}; } else { return {}; } } -std::list GestureConverter::exitHover(nsecs_t when, nsecs_t readTime, - float xCursorPosition, float yCursorPosition) { +std::list GestureConverter::exitHover(nsecs_t when, nsecs_t readTime) { if (mIsHovering) { mIsHovering = false; - return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT, xCursorPosition, - yCursorPosition)}; + return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT)}; } else { return {}; } } -NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action, - float xCursorPosition, float yCursorPosition) { +NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action) { PointerCoords coords; coords.clear(); - coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0); coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0); return makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState, - /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition); + /*pointerCount=*/1, &coords); } NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, uint32_t pointerCount, - const PointerCoords* pointerCoords, - float xCursorPosition, float yCursorPosition) { + const PointerCoords* pointerCoords) { return {mReaderContext.getNextId(), when, readTime, @@ -695,8 +641,8 @@ NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime pointerCoords, /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, - xCursorPosition, - yCursorPosition, + /* xCursorPosition= */ 0.f, + /* yCursorPosition= */ 0.f, /* downTime= */ mDownTime, /* videoFrames= */ {}}; } diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index c8f437e0b8..e6ced0f02c 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -20,7 +20,6 @@ #include #include -#include #include #include @@ -41,8 +40,7 @@ using std::chrono_literals::operator""ms; */ constexpr std::chrono::nanoseconds TAP_ENABLE_DELAY_NANOS = 400ms; -// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate -// PointerController calls. +// Converts Gesture structs from the gestures library into NotifyArgs. class GestureConverter { public: GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext, @@ -85,18 +83,14 @@ private: const Gesture& gesture); [[nodiscard]] std::list endPinch(nsecs_t when, nsecs_t readTime); - [[nodiscard]] std::list enterHover(nsecs_t when, nsecs_t readTime, - float xCursorPosition, float yCursorPosition); - [[nodiscard]] std::list exitHover(nsecs_t when, nsecs_t readTime, - float xCursorPosition, float yCursorPosition); + [[nodiscard]] std::list enterHover(nsecs_t when, nsecs_t readTime); + [[nodiscard]] std::list exitHover(nsecs_t when, nsecs_t readTime); - NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action, - float xCursorPosition, float yCursorPosition); + NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action); NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action, int32_t actionButton, int32_t buttonState, - uint32_t pointerCount, const PointerCoords* pointerCoords, - float xCursorPosition, float yCursorPosition); + uint32_t pointerCount, const PointerCoords* pointerCoords); void enableTapToClick(nsecs_t when); bool mIsHoverCancelled{false}; @@ -104,7 +98,6 @@ private: const int32_t mDeviceId; InputReaderContext& mReaderContext; - std::shared_ptr mPointerController; const bool mEnableFlingStop; std::optional mDisplayId; diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 6a009b2918..f50f5173b8 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -51,13 +51,11 @@ using testing::Each; using testing::ElementsAre; using testing::VariantWith; -class GestureConverterTestBase : public testing::Test { +class GestureConverterTest : public testing::Test { protected: static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000; static constexpr int32_t EVENTHUB_ID = 1; static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2; - static constexpr float POINTER_X = 500; - static constexpr float POINTER_Y = 200; void SetUp() { mFakeEventHub = std::make_unique(); @@ -68,1493 +66,31 @@ protected: mDevice = newDevice(); mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, -500, 500, 0, 0, 20); mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 500, 0, 0, 20); + } - mFakePointerController = std::make_shared( - /*enabled=*/!input_flags::enable_pointer_choreographer()); - mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1); - mFakePointerController->setPosition(POINTER_X, POINTER_Y); - mFakePolicy->setPointerController(mFakePointerController); - } - - std::shared_ptr newDevice() { - InputDeviceIdentifier identifier; - identifier.name = "device"; - identifier.location = "USB1"; - identifier.bus = 0; - std::shared_ptr device = - std::make_shared(mReader->getContext(), DEVICE_ID, /* generation= */ 2, - identifier); - mReader->pushNextDevice(device); - mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD, - identifier.bus); - mReader->loopOnce(); - return device; - } - - std::shared_ptr mFakeEventHub; - sp mFakePolicy; - std::unique_ptr mFakeListener; - std::unique_ptr mReader; - std::shared_ptr mDevice; - std::shared_ptr mFakePointerController; -}; - -class GestureConverterTest : public GestureConverterTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(false); - GestureConverterTestBase::SetUp(); - } -}; - -TEST_F(GestureConverterTest, Move) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0, 0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X - 5, POINTER_Y + 10), - WithRelativeMotion(-5, 10), WithButtonState(0), - WithPressure(0.0f))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); - - // The same gesture again should only repeat the HOVER_MOVE and cursor position change, not the - // HOVER_ENTER. - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X - 10, POINTER_Y + 20), - WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 10, POINTER_Y + 20)); -} - -TEST_F(GestureConverterTest, Move_Rotated) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0, 0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithCoords(POINTER_X + 10, POINTER_Y + 5), - WithRelativeMotion(10, 5), WithButtonState(0), - WithPressure(0.0f))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5)); -} - -TEST_F(GestureConverterTest, ButtonsChange) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - // Press left and right buttons at once - Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, - /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | - AMOTION_EVENT_BUTTON_SECONDARY))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | - AMOTION_EVENT_BUTTON_SECONDARY))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - // Then release the left button - Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, - /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), - WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - // Finally release the right button - Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT, - /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_UP)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithButtonState(0), - WithCoords(POINTER_X, POINTER_Y), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - - Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE, - /*is_tap=*/false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); - ASSERT_THAT(args.front(), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0), - WithCoords(POINTER_X - 5, POINTER_Y + 10), - WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)))); -} - -TEST_F(GestureConverterTest, DragWithButton) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - // Press the button - Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE, - /* is_tap= */ false); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - // Move - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10), - WithToolType(ToolType::FINGER), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10)); - - // Release the button - Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT, - /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_UP)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithButtonState(0), - WithCoords(POINTER_X - 5, POINTER_Y + 10), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Scroll) { - const nsecs_t downTime = 12345; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list args = - converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithCoords(POINTER_X, POINTER_Y), - WithGestureScrollDistance(0, 0, EPSILON), - WithDownTime(downTime))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X, POINTER_Y - 10), - WithGestureScrollDistance(0, 10, EPSILON))))); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X, POINTER_Y - 15), - WithGestureScrollDistance(0, 5, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, - GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithCoords(POINTER_X, POINTER_Y - 15), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification( - MotionClassification::TWO_FINGER_SWIPE), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Scroll_Rotated) { - const nsecs_t downTime = 12345; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list args = - converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithCoords(POINTER_X, POINTER_Y), - WithGestureScrollDistance(0, 0, EPSILON), - WithDownTime(downTime))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X - 10, POINTER_Y), - WithGestureScrollDistance(0, 10, EPSILON))))); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithCoords(POINTER_X - 15, POINTER_Y), - WithGestureScrollDistance(0, 5, EPSILON), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, - GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithCoords(POINTER_X - 15, POINTER_Y), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification( - MotionClassification::TWO_FINGER_SWIPE))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, - GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionClassification(MotionClassification::NONE), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, - GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - - // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we - // need to use another gesture type, like pinch. - Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture); - ASSERT_FALSE(args.empty()); - EXPECT_THAT(std::get(args.front()), WithGestureScrollDistance(0, 0, EPSILON)); -} - -TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, - /*dy=*/0); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5, - /*dy=*/10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - WithMotionClassification(MotionClassification::NONE)))); -} - -TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5, - /*dy=*/5); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); - - // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we - // need to use another gesture type, like pinch. - Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture); - ASSERT_FALSE(args.empty()); - EXPECT_THAT(std::get(args.front()), - AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0))); -} - -TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { - // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you - // start swiping up and then start moving left or right, it'll return gesture events with only Y - // deltas until you lift your fingers and start swiping again. That's why each of these tests - // only checks movement in one dimension. - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, - /* dy= */ 10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_EQ(4u, args.size()); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - // Three fake fingers should be created. We don't actually care where they are, so long as they - // move appropriately. - NotifyMotionArgs arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u))); - PointerCoords finger0Start = arg.pointerCoords[0]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); - PointerCoords finger1Start = arg.pointerCoords[1]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); - PointerCoords finger2Start = arg.pointerCoords[2]; - args.pop_front(); - - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u))); - EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); - EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); - EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); - EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10); - EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10); - EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10); - - Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* dx= */ 0, /* dy= */ 5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); - EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); - EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); - EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15); - EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15); - EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15); - - Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(3), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, - /* dy= */ 10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_EQ(4u, args.size()); - ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ADISPLAY_ID_DEFAULT)))); - - // Three fake fingers should be created. We don't actually care where they are, so long as they - // move appropriately. - NotifyMotionArgs arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u))); - PointerCoords finger0Start = arg.pointerCoords[0]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); - PointerCoords finger1Start = arg.pointerCoords[1]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); - PointerCoords finger2Start = arg.pointerCoords[2]; - args.pop_front(); - - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u))); - EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10); - EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10); - EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10); - EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY()); - EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY()); - EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); - - Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* dx= */ 0, /* dy= */ 5); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15); - EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15); - EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15); - EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY()); - EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY()); - EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); - - Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); - ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ADISPLAY_ID_DEFAULT)))); -} - -TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* dx= */ 10, /* dy= */ 0); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_EQ(5u, args.size()); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - // Four fake fingers should be created. We don't actually care where they are, so long as they - // move appropriately. - NotifyMotionArgs arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON), - WithPointerCount(1u))); - PointerCoords finger0Start = arg.pointerCoords[0]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))); - PointerCoords finger1Start = arg.pointerCoords[1]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))); - PointerCoords finger2Start = arg.pointerCoords[2]; - args.pop_front(); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | - 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), WithPointerCount(4u))); - PointerCoords finger3Start = arg.pointerCoords[3]; - args.pop_front(); - - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0.01, 0, EPSILON), WithPointerCount(4u))); - EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10); - EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10); - EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10); - EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10); - EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY()); - EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY()); - EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); - EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY()); - - Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* dx= */ 5, /* dy= */ 0); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); - ASSERT_EQ(1u, args.size()); - arg = std::get(args.front()); - ASSERT_THAT(arg, - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4), - WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15); - EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15); - EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15); - EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15); - EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY()); - EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY()); - EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY()); - EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY()); - - Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(4u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithGestureOffset(0, 0, EPSILON), - WithGestureSwipeFingerCount(4), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Pinch_Inwards) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, - GESTURES_ZOOM_START); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithCoords(POINTER_X - 100, POINTER_Y), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithPointerCoords(1, POINTER_X + 100, POINTER_Y), - WithPointerCount(2u))))); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(0.8f, EPSILON), - WithPointerCoords(0, POINTER_X - 80, POINTER_Y), - WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, - GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Pinch_Outwards) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, - GESTURES_ZOOM_START); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithCoords(POINTER_X - 100, POINTER_Y), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_DOWN | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithPointerCoords(1, POINTER_X + 100, POINTER_Y), - WithPointerCount(2u))))); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* dz= */ 1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.2f, EPSILON), - WithPointerCoords(0, POINTER_X - 120, POINTER_Y), - WithPointerCoords(1, POINTER_X + 120, POINTER_Y), - WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, - GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_START); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /*dz=*/1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); - - Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - WithMotionClassification(MotionClassification::NONE)))); -} - -TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_START); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /*dz=*/1.2, GESTURES_ZOOM_UPDATE); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture); - - Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_END); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture); - - // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we - // need to use another gesture type, like scroll. - Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1, - /*dy=*/0); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); - ASSERT_FALSE(args.empty()); - EXPECT_THAT(std::get(args.front()), WithGesturePinchScaleFactor(0, EPSILON)); -} - -TEST_F(GestureConverterTest, ResetWithButtonPressed) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, - /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture); - - std::list args = converter.reset(ARBITRARY_TIME); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), - WithButtonState(0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithButtonState(0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithButtonState(0))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, ResetDuringScroll) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - std::list args = converter.reset(ARBITRARY_TIME); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithCoords(POINTER_X, POINTER_Y - 10), - WithGestureScrollDistance(0, 0, EPSILON), - WithMotionClassification( - MotionClassification::TWO_FINGER_SWIPE), - WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, - /*dy=*/10); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - std::list args = converter.reset(ARBITRARY_TIME); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(3u))), - VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithGestureOffset(0, 0, EPSILON), - WithMotionClassification( - MotionClassification::MULTI_FINGER_SWIPE), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, ResetDuringPinch) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, - GESTURES_ZOOM_START); - (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); - - std::list args = converter.reset(ARBITRARY_TIME); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction( - AMOTION_EVENT_ACTION_POINTER_UP | - 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCount(2u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithMotionClassification(MotionClassification::PINCH), - WithGesturePinchScaleFactor(1.0f, EPSILON), - WithPointerCount(1u))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), - WithMotionClassification(MotionClassification::NONE))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, FlingTapDown) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); - - ASSERT_THAT(std::get(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))); - - ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y)); - ASSERT_TRUE(mFakePointerController->isPointerShown()); -} - -TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - input_flags::enable_touchpad_fling_stop(true); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture); - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, - GESTURES_FLING_START); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - - Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_DOWN)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT), - WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE))))); -} - -TEST_F(GestureConverterTest, Tap) { - // Tap should produce button press/release events - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - // We don't need to check args here, since it's covered by the FlingTapDown test. - - Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture); - - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), - WithButtonState(0), WithPressure(0.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(0), WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithButtonState(0), WithPressure(0.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithButtonState(0), WithPressure(0.0f))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F(GestureConverterTest, Click) { - // Click should produce button press/release events - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - // We don't need to check args here, since it's covered by the FlingTapDown test. - - Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture); - - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), - WithButtonState(0), WithPressure(0.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_NONE, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithPressure(0.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithPressure(0.0f))))); - ASSERT_THAT(args, - Each(VariantWith( - AllOf(WithButtonState(0), WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); -} - -TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION), - REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) { - nsecs_t currentTime = ARBITRARY_GESTURE_TIME; - - // Tap should be ignored when disabled - mReader->getContext()->setPreventingTouchpadTaps(true); - - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list args = - converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); - // We don't need to check args here, since it's covered by the FlingTapDown test. - - Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); - - // no events should be generated - ASSERT_EQ(0u, args.size()); - - // Future taps should be re-enabled - ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); -} - -TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { - nsecs_t currentTime = ARBITRARY_GESTURE_TIME; - - // Tap should be ignored when disabled - mReader->getContext()->setPreventingTouchpadTaps(true); - - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list args = - converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); - // We don't need to check args here, since it's covered by the FlingTapDown test. - - Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); - - // no events should be generated - ASSERT_EQ(0u, args.size()); - - // Future taps should be re-enabled - ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); - - // taps before the threshold should still be ignored - currentTime += TAP_ENABLE_DELAY_NANOS.count(); - flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0))); - - tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); - - // no events should be generated - ASSERT_EQ(0u, args.size()); - - // taps after the threshold should be recognised - currentTime += 1; - flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture); - - ASSERT_EQ(1u, args.size()); - ASSERT_THAT(std::get(args.front()), - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0))); - - tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true); - args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), - WithButtonState(0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithButtonState(0))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithButtonState(0))))); - ASSERT_THAT(args, Each(VariantWith(WithRelativeMotion(0.f, 0.f)))); -} - -TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) { - // Click should still produce button press/release events - mReader->getContext()->setPreventingTouchpadTaps(true); - - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, - /* vy= */ 0, GESTURES_FLING_TAP_DOWN); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); - // We don't need to check args here, since it's covered by the FlingTapDown test. - - Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_LEFT, - /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), - WithButtonState(0), WithPressure(0.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), - WithPressure(1.0f))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, - /* down= */ GESTURES_BUTTON_NONE, - /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false); - args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture); - - ASSERT_THAT(args, - ElementsAre(VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), - WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), - WithButtonState(0), WithPressure(1.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), - WithButtonState(0), WithPressure(0.0f))), - VariantWith( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithButtonState(0), WithPressure(0.0f))))); - ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(POINTER_X, POINTER_Y), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); - - // Future taps should be re-enabled - ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); -} - -TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) { - // initially disable tap-to-click - mReader->getContext()->setPreventingTouchpadTaps(true); - - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); - std::list args = - converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); - // We don't need to check args here, since it's covered by the Move test. - - // Future taps should be re-enabled - ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); -} - -TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove, - REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { - const nsecs_t gestureStartTime = 1000; - InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); - GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); - - // Start a move gesture at gestureStartTime - Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10); - std::list args = - converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))); - - // Key presses with IME connection should cancel ongoing move gesture - nsecs_t currentTime = gestureStartTime + 100; - mFakePolicy->setIsInputMethodConnectionActive(true); - mReader->getContext()->setLastKeyDownTimestamp(currentTime); - moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); - args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)))); - - // any updates in existing move gesture should be ignored - moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); - args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture); - ASSERT_EQ(0u, args.size()); - - // New gesture should not be affected - currentTime += 100; - moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10); - args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture); - ASSERT_THAT(args, - ElementsAre(VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)), - VariantWith( - WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))); -} - -// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging -// logic can be removed. -class GestureConverterTestWithChoreographer : public GestureConverterTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(true); - GestureConverterTestBase::SetUp(); + std::shared_ptr newDevice() { + InputDeviceIdentifier identifier; + identifier.name = "device"; + identifier.location = "USB1"; + identifier.bus = 0; + std::shared_ptr device = + std::make_shared(mReader->getContext(), DEVICE_ID, /* generation= */ 2, + identifier); + mReader->pushNextDevice(device); + mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD, + identifier.bus); + mReader->loopOnce(); + return device; } + + std::shared_ptr mFakeEventHub; + sp mFakePolicy; + std::unique_ptr mFakeListener; + std::unique_ptr mReader; + std::shared_ptr mDevice; }; -TEST_F(GestureConverterTestWithChoreographer, Move) { +TEST_F(GestureConverterTest, Move) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1585,7 +121,7 @@ TEST_F(GestureConverterTestWithChoreographer, Move) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) { +TEST_F(GestureConverterTest, Move_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); @@ -1608,7 +144,7 @@ TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) { +TEST_F(GestureConverterTest, ButtonsChange) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1670,7 +206,7 @@ TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, ButtonDownAfterMoveExitsHover) { +TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1690,7 +226,7 @@ TEST_F(GestureConverterTestWithChoreographer, ButtonDownAfterMoveExitsHover) { WithDisplayId(ADISPLAY_ID_DEFAULT)))); } -TEST_F(GestureConverterTestWithChoreographer, DragWithButton) { +TEST_F(GestureConverterTest, DragWithButton) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1743,7 +279,7 @@ TEST_F(GestureConverterTestWithChoreographer, DragWithButton) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Scroll) { +TEST_F(GestureConverterTest, Scroll) { const nsecs_t downTime = 12345; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); @@ -1800,7 +336,7 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) { +TEST_F(GestureConverterTest, Scroll_Rotated) { const nsecs_t downTime = 12345; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); @@ -1855,7 +391,7 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) { +TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1879,7 +415,7 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGe WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) { +TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1904,7 +440,7 @@ TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGe EXPECT_THAT(std::get(args.front()), WithGestureScrollDistance(0, 0, EPSILON)); } -TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificationAfterGesture) { +TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1925,7 +461,7 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificat WithMotionClassification(MotionClassification::NONE)))); } -TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { +TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -1948,7 +484,7 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxes AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0))); } -TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) { +TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you // start swiping up and then start moving left or right, it'll return gesture events with only Y // deltas until you lift your fingers and start swiping again. That's why each of these tests @@ -2057,7 +593,7 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) { +TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); @@ -2141,7 +677,7 @@ TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) { ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ADISPLAY_ID_DEFAULT)))); } -TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) { +TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2266,7 +802,7 @@ TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) { +TEST_F(GestureConverterTest, Pinch_Inwards) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2328,7 +864,7 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) { +TEST_F(GestureConverterTest, Pinch_Outwards) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2390,7 +926,7 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) { +TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2415,7 +951,7 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGes WithMotionClassification(MotionClassification::NONE)))); } -TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) { +TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2442,7 +978,7 @@ TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGestur EXPECT_THAT(std::get(args.front()), WithGesturePinchScaleFactor(0, EPSILON)); } -TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) { +TEST_F(GestureConverterTest, ResetWithButtonPressed) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2474,7 +1010,7 @@ TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) { +TEST_F(GestureConverterTest, ResetDuringScroll) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2500,7 +1036,7 @@ TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) { +TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2541,7 +1077,7 @@ TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) { +TEST_F(GestureConverterTest, ResetDuringPinch) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2573,7 +1109,7 @@ TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) { +TEST_F(GestureConverterTest, FlingTapDown) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setDisplayId(ADISPLAY_ID_DEFAULT); @@ -2589,7 +1125,7 @@ TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) { WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); } -TEST_F(GestureConverterTestWithChoreographer, FlingTapDownAfterScrollStopsFling) { +TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); @@ -2621,7 +1157,7 @@ TEST_F(GestureConverterTestWithChoreographer, FlingTapDownAfterScrollStopsFling) WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE))))); } -TEST_F(GestureConverterTestWithChoreographer, Tap) { +TEST_F(GestureConverterTest, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); @@ -2668,7 +1204,7 @@ TEST_F(GestureConverterTestWithChoreographer, Tap) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F(GestureConverterTestWithChoreographer, Click) { +TEST_F(GestureConverterTest, Click) { // Click should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); @@ -2727,7 +1263,7 @@ TEST_F(GestureConverterTestWithChoreographer, Click) { WithDisplayId(ADISPLAY_ID_DEFAULT))))); } -TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled, +TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION), REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) { nsecs_t currentTime = ARBITRARY_GESTURE_TIME; @@ -2757,7 +1293,7 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabl ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); } -TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabledWithDelay, +TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay, REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { nsecs_t currentTime = ARBITRARY_GESTURE_TIME; @@ -2841,7 +1377,7 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabl ASSERT_THAT(args, Each(VariantWith(WithRelativeMotion(0.f, 0.f)))); } -TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled, +TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) { // Click should still produce button press/release events mReader->getContext()->setPreventingTouchpadTaps(true); @@ -2909,7 +1445,7 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisa ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); } -TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick, +TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick, REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) { // initially disable tap-to-click mReader->getContext()->setPreventingTouchpadTaps(true); @@ -2927,7 +1463,7 @@ TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick, ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); } -TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, KeypressCancelsHoverMove, +TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove, REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) { const nsecs_t gestureStartTime = 1000; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index a92dce5dd0..402654c7ac 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -19,9 +19,7 @@ #include #include -#include #include -#include "FakePointerController.h" #include "InputMapperTest.h" #include "InterfaceMocks.h" #include "TestEventMatchers.h" @@ -44,12 +42,10 @@ constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; constexpr std::optional NO_PORT = std::nullopt; // no physical port is specified -namespace input_flags = com::android::input::flags; - /** * Unit tests for TouchpadInputMapper. */ -class TouchpadInputMapperTestBase : public InputMapperUnitTest { +class TouchpadInputMapperTest : public InputMapperUnitTest { protected: void SetUp() override { InputMapperUnitTest::SetUp(); @@ -117,18 +113,7 @@ protected: return base::ResultError("Axis not supported", NAME_NOT_FOUND); }); createDevice(); - mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration, - isPointerChoreographerEnabled()); - } - - virtual bool isPointerChoreographerEnabled() { return false; } -}; - -class TouchpadInputMapperTest : public TouchpadInputMapperTestBase { -protected: - void SetUp() override { - input_flags::enable_pointer_choreographer(false); - TouchpadInputMapperTestBase::SetUp(); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); } }; @@ -139,66 +124,6 @@ protected: * but only after the button is released. */ TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) { - std::list args; - - args += process(EV_ABS, ABS_MT_TRACKING_ID, 1); - args += process(EV_KEY, BTN_TOUCH, 1); - setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER}); - args += process(EV_KEY, BTN_TOOL_FINGER, 1); - args += process(EV_ABS, ABS_MT_POSITION_X, 50); - args += process(EV_ABS, ABS_MT_POSITION_Y, 50); - args += process(EV_ABS, ABS_MT_PRESSURE, 1); - args += process(EV_SYN, SYN_REPORT, 0); - ASSERT_THAT(args, testing::IsEmpty()); - - // Without this sleep, the test fails. - // TODO(b/284133337): Figure out whether this can be removed - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - - args += process(EV_KEY, BTN_LEFT, 1); - setScanCodeState(KeyState::DOWN, {BTN_LEFT}); - args += process(EV_SYN, SYN_REPORT, 0); - - args += process(EV_KEY, BTN_LEFT, 0); - setScanCodeState(KeyState::UP, {BTN_LEFT}); - args += process(EV_SYN, SYN_REPORT, 0); - ASSERT_THAT(args, - ElementsAre(VariantWith(WithMotionAction(HOVER_ENTER)), - VariantWith(WithMotionAction(HOVER_MOVE)), - VariantWith(WithMotionAction(HOVER_EXIT)), - VariantWith(WithMotionAction(ACTION_DOWN)), - VariantWith(WithMotionAction(BUTTON_PRESS)), - VariantWith(WithMotionAction(BUTTON_RELEASE)), - VariantWith(WithMotionAction(ACTION_UP)), - VariantWith(WithMotionAction(HOVER_ENTER)))); - - // Liftoff - args.clear(); - args += process(EV_ABS, ABS_MT_PRESSURE, 0); - args += process(EV_ABS, ABS_MT_TRACKING_ID, -1); - args += process(EV_KEY, BTN_TOUCH, 0); - setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER}); - args += process(EV_KEY, BTN_TOOL_FINGER, 0); - args += process(EV_SYN, SYN_REPORT, 0); - ASSERT_THAT(args, testing::IsEmpty()); -} - -class TouchpadInputMapperTestWithChoreographer : public TouchpadInputMapperTestBase { -protected: - void SetUp() override { TouchpadInputMapperTestBase::SetUp(); } - - bool isPointerChoreographerEnabled() override { return true; } -}; - -// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging -// logic can be removed. -/** - * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is - * generated when hovering stops. Currently, it is not. - * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away, - * but only after the button is released. - */ -TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) { mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID); mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); -- GitLab From 0b4891a9a5de0f00f1432520d5db6d1eef4b66e5 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 3 May 2024 23:19:44 +0000 Subject: [PATCH 302/465] Mouse: Cleanup after PointerChoreographer refactor Bug: 311416205 Test: atest inputflinger_tests Change-Id: I4be21303d0c809a9e8e14854faf213a0d41fddf1 --- .../reader/mapper/CursorInputMapper.cpp | 61 +- .../reader/mapper/CursorInputMapper.h | 12 +- .../tests/CursorInputMapper_test.cpp | 550 +----------------- .../inputflinger/tests/InputMapperTest.cpp | 6 - services/inputflinger/tests/InputMapperTest.h | 3 - 5 files changed, 27 insertions(+), 605 deletions(-) diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index c8cc5dcc83..becac5a671 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -40,8 +40,6 @@ namespace android { // The default velocity control parameters that has no effect. static const VelocityControlParameters FLAT_VELOCITY_CONTROL_PARAMS{}; -static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer(); - // --- CursorMotionAccumulator --- CursorMotionAccumulator::CursorMotionAccumulator() { @@ -78,22 +76,10 @@ void CursorMotionAccumulator::finishSync() { CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig) - : CursorInputMapper(deviceContext, readerConfig, ENABLE_POINTER_CHOREOGRAPHER) {} - -CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig, - bool enablePointerChoreographer) : InputMapper(deviceContext, readerConfig), mLastEventTime(std::numeric_limits::min()), - mEnablePointerChoreographer(enablePointerChoreographer), mEnableNewMousePointerBallistics(input_flags::enable_new_mouse_pointer_ballistics()) {} -CursorInputMapper::~CursorInputMapper() { - if (mPointerController != nullptr) { - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); - } -} - uint32_t CursorInputMapper::getSources() const { return mSource; } @@ -304,22 +290,6 @@ std::list CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mSource == AINPUT_SOURCE_MOUSE) { - if (!mEnablePointerChoreographer) { - if (moved || scrolled || buttonsChanged) { - mPointerController->setPresentation( - PointerControllerInterface::Presentation::POINTER); - - if (moved) { - mPointerController->move(deltaX, deltaY); - } - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - } - - std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); - - pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition); - pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition); - } pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY); } else { @@ -470,7 +440,6 @@ void CursorInputMapper::configureBasicParams() { mYPrecision = 1.0f; mXScale = 1.0f; mYScale = 1.0f; - mPointerController = getContext()->getPointerController(getDeviceId()); break; case Parameters::Mode::NAVIGATION: mSource = AINPUT_SOURCE_TRACKBALL; @@ -490,8 +459,6 @@ void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration if (mParameters.mode == Parameters::Mode::POINTER) { mParameters.mode = Parameters::Mode::POINTER_RELATIVE; mSource = AINPUT_SOURCE_MOUSE_RELATIVE; - // Keep PointerController around in order to preserve the pointer position. - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); } else { ALOGE("Cannot request pointer capture, device is not in MODE_POINTER"); } @@ -546,32 +513,12 @@ void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfigurat // Only generate events for the associated display. mDisplayId = assocViewport->displayId; resolvedViewport = *assocViewport; - if (!mEnablePointerChoreographer) { - const bool mismatchedPointerDisplay = - isPointer && (assocViewport->displayId != mPointerController->getDisplayId()); - if (mismatchedPointerDisplay) { - // This device's associated display doesn't match PointerController's current - // display. Do not associate it with any display. - mDisplayId.reset(); - } - } } else if (isPointer) { // The InputDevice is not associated with a viewport, but it controls the mouse pointer. - if (mEnablePointerChoreographer) { - // Always use DISPLAY_ID_NONE for mouse events. - // PointerChoreographer will make it target the correct the displayId later. - resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); - mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; - } else { - mDisplayId = mPointerController->getDisplayId(); - if (auto v = config.getDisplayViewportById(*mDisplayId); v) { - resolvedViewport = *v; - } - if (auto bounds = mPointerController->getBounds(); bounds) { - mBoundsInLogicalDisplay = *bounds; - isBoundsSet = true; - } - } + // Always use DISPLAY_ID_NONE for mouse events. + // PointerChoreographer will make it target the correct the displayId later. + resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); + mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; } mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) || diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index ca541d9924..3daae2fe1d 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -20,14 +20,11 @@ #include "CursorScrollAccumulator.h" #include "InputMapper.h" -#include #include +#include #include - #include namespace android { namespace { -template -inline T load(const void* p) { - static_assert(std::is_integral::value, "T must be integral"); - - T r; - std::memcpy(&r, p, sizeof(r)); - return r; -} - -uint64_t rotateByAtLeast1(uint64_t val, uint8_t shift) { - return (val >> shift) | (val << (64 - shift)); -} - -uint64_t shiftMix(uint64_t val) { - return val ^ (val >> 47); -} - -__attribute__((no_sanitize("unsigned-integer-overflow"))) -uint64_t hash64Len16(uint64_t u, uint64_t v) { - constexpr uint64_t kMul = 0x9ddfea08eb382d69; - uint64_t a = (u ^ v) * kMul; - a ^= (a >> 47); - uint64_t b = (v ^ a) * kMul; - b ^= (b >> 47); - b *= kMul; - return b; -} - -__attribute__((no_sanitize("unsigned-integer-overflow"))) -uint64_t hash64Len0To16(const char* s, uint64_t len) { - constexpr uint64_t k2 = 0x9ae16a3b2f90404f; - constexpr uint64_t k3 = 0xc949d7c7509e6557; - - if (len > 8) { - const uint64_t a = load(s); - const uint64_t b = load(s + len - 8); - return hash64Len16(a, rotateByAtLeast1(b + len, static_cast(len))) ^ b; - } - if (len >= 4) { - const uint32_t a = load(s); - const uint32_t b = load(s + len - 4); - return hash64Len16(len + (a << 3), b); - } - if (len > 0) { - const unsigned char a = static_cast(s[0]); - const unsigned char b = static_cast(s[len >> 1]); - const unsigned char c = static_cast(s[len - 1]); - const uint32_t y = static_cast(a) + (static_cast(b) << 8); - const uint32_t z = static_cast(len) + (static_cast(c) << 2); - return shiftMix(y * k2 ^ z * k3) * k2; - } - return k2; -} - using byte_view = std::span; constexpr size_t kEdidBlockSize = 128; @@ -320,7 +266,7 @@ std::optional parseEdid(const DisplayIdentificationData& edid) { // Hash model string instead of using product code or (integer) serial number, since the latter // have been observed to change on some displays with multiple inputs. Use a stable hash instead // of std::hash which is only required to be same within a single execution of a program. - const uint32_t modelHash = static_cast(cityHash64Len0To16(modelString)); + const uint32_t modelHash = static_cast(*ftl::stable_hash(modelString)); // Parse extension blocks. std::optional cea861Block; @@ -394,13 +340,4 @@ PhysicalDisplayId getVirtualDisplayId(uint32_t id) { return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id); } -uint64_t cityHash64Len0To16(std::string_view sv) { - auto len = sv.length(); - if (len > 16) { - ALOGE("%s called with length %zu. Only hashing the first 16 chars", __FUNCTION__, len); - len = 16; - } - return hash64Len0To16(sv.data(), len); -} - } // namespace android diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h index fc9c0f491b..8bc2017b55 100644 --- a/libs/ui/include/ui/DisplayIdentification.h +++ b/libs/ui/include/ui/DisplayIdentification.h @@ -80,7 +80,4 @@ std::optional parseDisplayIdentificationData( PhysicalDisplayId getVirtualDisplayId(uint32_t id); -// CityHash64 implementation that only hashes at most the first 16 characters of the given string. -uint64_t cityHash64Len0To16(std::string_view sv); - } // namespace android diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp index 736979a7c5..721b46688e 100644 --- a/libs/ui/tests/DisplayIdentification_test.cpp +++ b/libs/ui/tests/DisplayIdentification_test.cpp @@ -21,9 +21,9 @@ #include #include +#include #include #include - #include using ::testing::ElementsAre; @@ -135,7 +135,7 @@ DisplayIdentificationData asDisplayIdentificationData(const unsigned char (&byte } uint32_t hash(const char* str) { - return static_cast(cityHash64Len0To16(str)); + return static_cast(*ftl::stable_hash(str)); } } // namespace @@ -188,6 +188,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_STREQ("SEC", edid->pnpId.data()); // ASCII text should be used as fallback if display name and serial number are missing. EXPECT_EQ(hash("121AT11-801"), edid->modelHash); + EXPECT_EQ(hash("121AT11-801"), 626564263); EXPECT_TRUE(edid->displayName.empty()); EXPECT_EQ(12610, edid->productId); EXPECT_EQ(21, edid->manufactureOrModelYear); @@ -199,6 +200,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(0x22f0u, edid->manufacturerId); EXPECT_STREQ("HWP", edid->pnpId.data()); EXPECT_EQ(hash("HP ZR30w"), edid->modelHash); + EXPECT_EQ(hash("HP ZR30w"), 918492362); EXPECT_EQ("HP ZR30w", edid->displayName); EXPECT_EQ(10348, edid->productId); EXPECT_EQ(22, edid->manufactureOrModelYear); @@ -210,6 +212,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(0x4c2du, edid->manufacturerId); EXPECT_STREQ("SAM", edid->pnpId.data()); EXPECT_EQ(hash("SAMSUNG"), edid->modelHash); + EXPECT_EQ(hash("SAMSUNG"), 1201368132); EXPECT_EQ("SAMSUNG", edid->displayName); EXPECT_EQ(2302, edid->productId); EXPECT_EQ(21, edid->manufactureOrModelYear); @@ -227,6 +230,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(13481, edid->manufacturerId); EXPECT_STREQ("MEI", edid->pnpId.data()); EXPECT_EQ(hash("Panasonic-TV"), edid->modelHash); + EXPECT_EQ(hash("Panasonic-TV"), 3876373262); EXPECT_EQ("Panasonic-TV", edid->displayName); EXPECT_EQ(41622, edid->productId); EXPECT_EQ(29, edid->manufactureOrModelYear); @@ -244,6 +248,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(8355, edid->manufacturerId); EXPECT_STREQ("HEC", edid->pnpId.data()); EXPECT_EQ(hash("Hisense"), edid->modelHash); + EXPECT_EQ(hash("Hisense"), 2859844809); EXPECT_EQ("Hisense", edid->displayName); EXPECT_EQ(0, edid->productId); EXPECT_EQ(29, edid->manufactureOrModelYear); @@ -261,6 +266,7 @@ TEST(DisplayIdentificationTest, parseEdid) { EXPECT_EQ(3724, edid->manufacturerId); EXPECT_STREQ("CTL", edid->pnpId.data()); EXPECT_EQ(hash("LP2361"), edid->modelHash); + EXPECT_EQ(hash("LP2361"), 1523181158); EXPECT_EQ("LP2361", edid->displayName); EXPECT_EQ(9373, edid->productId); EXPECT_EQ(23, edid->manufactureOrModelYear); @@ -281,6 +287,7 @@ TEST(DisplayIdentificationTest, parseInvalidEdid) { // Serial number should be used as fallback if display name is invalid. const auto modelHash = hash("CN4202137Q"); EXPECT_EQ(modelHash, edid->modelHash); + EXPECT_EQ(modelHash, 3582951527); EXPECT_TRUE(edid->displayName.empty()); // Parsing should succeed even if EDID is truncated. -- GitLab From 077c4fb8633f207b4bd3eb6a3980248e9d227e55 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Sat, 4 May 2024 20:32:22 +0000 Subject: [PATCH 305/465] Remove dup codes Bug: N/A Change-Id: I9d53012e81c0c79fb2d9a03a6ccaae86089a0db7 Test: builds --- .../tests/LayerRenderTypeTransaction_test.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp index 2b1834d8e4..4b3ad8ad5a 100644 --- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp +++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp @@ -154,8 +154,6 @@ void LayerRenderTypeTransactionTest::setRelativeZBasicHelper(uint32_t layerType) switch (layerType) { case ISurfaceComposerClient::eFXSurfaceBufferQueue: - Transaction().setPosition(layerG, 16, 16).setRelativeLayer(layerG, layerR, 1).apply(); - break; case ISurfaceComposerClient::eFXSurfaceBufferState: Transaction().setPosition(layerG, 16, 16).setRelativeLayer(layerG, layerR, 1).apply(); break; @@ -200,13 +198,6 @@ void LayerRenderTypeTransactionTest::setRelativeZGroupHelper(uint32_t layerType) // layerR = 0, layerG = layerR + 3, layerB = 2 switch (layerType) { case ISurfaceComposerClient::eFXSurfaceBufferQueue: - Transaction() - .setPosition(layerG, 8, 8) - .setRelativeLayer(layerG, layerR, 3) - .setPosition(layerB, 16, 16) - .setLayer(layerB, mLayerZBase + 2) - .apply(); - break; case ISurfaceComposerClient::eFXSurfaceBufferState: Transaction() .setPosition(layerG, 8, 8) @@ -413,13 +404,6 @@ void LayerRenderTypeTransactionTest::setAlphaBasicHelper(uint32_t layerType) { switch (layerType) { case ISurfaceComposerClient::eFXSurfaceBufferQueue: - Transaction() - .setAlpha(layer1, 0.25f) - .setAlpha(layer2, 0.75f) - .setPosition(layer2, 16, 0) - .setLayer(layer2, mLayerZBase + 1) - .apply(); - break; case ISurfaceComposerClient::eFXSurfaceBufferState: Transaction() .setAlpha(layer1, 0.25f) -- GitLab From 802959c5a8ea2042cf1d11f4561ae7b7f42da4c7 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 6 May 2024 18:54:24 +0000 Subject: [PATCH 306/465] Add diag for host-based input code This will provide more informative errors when the sanitizer detects an issue. Bug: 271455682 Change-Id: I38f96477ae0019aadfa5e363ce828879dba19ef3 Test: m checkinput --- services/inputflinger/Android.bp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index a03055ff86..70801dccb0 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -56,6 +56,16 @@ cc_defaults { host: { sanitize: { address: true, + diag: { + cfi: true, + integer_overflow: true, + memtag_heap: true, + undefined: true, + misc_undefined: [ + "bounds", + "all", + ], + }, }, include_dirs: [ "bionic/libc/kernel/android/uapi/", -- GitLab From 3ed7e355dbb04405378bbc3e2e30bf7bda62628e Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 3 May 2024 23:59:43 +0000 Subject: [PATCH 307/465] Touch: Cleanup after PointerChoreographer refactor Bug: 311416205 Test: atest inputflinger_tests Change-Id: I8d05ce684abbcf1e3412165e3aa38980543a176a --- .../reader/mapper/TouchInputMapper.cpp | 208 ++-------------- .../reader/mapper/TouchInputMapper.h | 7 - .../inputflinger/tests/InputReader_test.cpp | 224 +----------------- 3 files changed, 20 insertions(+), 419 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 81449d1588..cf07506d52 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -336,7 +336,6 @@ std::list TouchInputMapper::reconfigure(nsecs_t when, changes.any(InputReaderConfiguration::Change::DISPLAY_INFO | InputReaderConfiguration::Change::POINTER_CAPTURE | InputReaderConfiguration::Change::POINTER_GESTURE_ENABLEMENT | - InputReaderConfiguration::Change::SHOW_TOUCHES | InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE | InputReaderConfiguration::Change::DEVICE_TYPE)) { // Configure device sources, display dimensions, orientation and @@ -1042,32 +1041,6 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) mOrientedRanges.clear(); } - // Create and preserve the pointer controller in the following cases: - const bool isPointerControllerNeeded = - // - when the device is in pointer mode, to show the mouse cursor; - (mDeviceMode == DeviceMode::POINTER) || - // - when pointer capture is enabled, to preserve the mouse cursor position; - (mParameters.deviceType == Parameters::DeviceType::POINTER && - mConfig.pointerCaptureRequest.isEnable()) || - // - when we should be showing touches; - (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) || - // - when we should be showing a pointer icon for direct styluses. - (mDeviceMode == DeviceMode::DIRECT && mConfig.stylusPointerIconEnabled && hasStylus()); - if (isPointerControllerNeeded) { - if (mPointerController == nullptr) { - mPointerController = getContext()->getPointerController(getDeviceId()); - } - if (mConfig.pointerCaptureRequest.isEnable()) { - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); - } - } else { - if (mPointerController != nullptr && mDeviceMode == DeviceMode::DIRECT && - !mConfig.showTouches) { - mPointerController->clearSpots(); - } - mPointerController.reset(); - } - if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) { ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %s, mode %s, " "display id %d", @@ -1400,7 +1373,6 @@ void TouchInputMapper::updateAffineTransformation() { std::list TouchInputMapper::reset(nsecs_t when) { std::list out = cancelTouch(when, when); - updateTouchSpots(); mCursorButtonAccumulator.reset(getDeviceContext()); mCursorScrollAccumulator.reset(getDeviceContext()); @@ -1427,11 +1399,6 @@ std::list TouchInputMapper::reset(nsecs_t when) { mPointerSimple.reset(); resetExternalStylus(); - if (mPointerController != nullptr) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - mPointerController->clearSpots(); - } - return out += InputMapper::reset(when); } @@ -1586,11 +1553,6 @@ std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t re uint32_t policyFlags = 0; bool buttonsPressed = mCurrentRawState.buttonState & ~mLastRawState.buttonState; if (initialDown || buttonsPressed) { - // If this is a touch screen, hide the pointer on an initial down. - if (mDeviceMode == DeviceMode::DIRECT) { - getContext()->fadePointer(); - } - if (mParameters.wake) { policyFlags |= POLICY_FLAG_WAKE; } @@ -1658,7 +1620,6 @@ std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t re out += dispatchPointerUsage(when, readTime, policyFlags, pointerUsage); } else { if (!mCurrentMotionAborted) { - updateTouchSpots(); out += dispatchButtonRelease(when, readTime, policyFlags); out += dispatchHoverExit(when, readTime, policyFlags); out += dispatchTouches(when, readTime, policyFlags); @@ -1690,28 +1651,6 @@ std::list TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t re return out; } -void TouchInputMapper::updateTouchSpots() { - if (!mConfig.showTouches || mPointerController == nullptr) { - return; - } - - // Update touch spots when this is a touchscreen even when it's not enabled so that we can - // clear touch spots. - if (mDeviceMode != DeviceMode::DIRECT && - (mDeviceMode != DeviceMode::DISABLED || !isTouchScreen())) { - return; - } - - mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT); - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - - mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(), - mCurrentCookedState.cookedPointerData.idToIndex.cbegin(), - mCurrentCookedState.cookedPointerData.touchingIdBits | - mCurrentCookedState.cookedPointerData.hoveringIdBits, - mViewport.displayId); -} - bool TouchInputMapper::isTouchScreen() { return mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN && mParameters.hasAssociatedDisplay; @@ -2560,54 +2499,6 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns cancelPreviousGesture = false; } - // Update the pointer presentation and spots. - if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) { - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - if (finishPreviousGesture || cancelPreviousGesture) { - mPointerController->clearSpots(); - } - - if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) { - mPointerController->setSpots(mPointerGesture.currentGestureCoords.cbegin(), - mPointerGesture.currentGestureIdToIndex.cbegin(), - mPointerGesture.currentGestureIdBits, - mPointerController->getDisplayId()); - } - } else { - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - } - - // Show or hide the pointer if needed. - switch (mPointerGesture.currentGestureMode) { - case PointerGesture::Mode::NEUTRAL: - case PointerGesture::Mode::QUIET: - if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH && - mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) { - // Remind the user of where the pointer is after finishing a gesture with spots. - mPointerController->unfade(PointerControllerInterface::Transition::GRADUAL); - } - break; - case PointerGesture::Mode::TAP: - case PointerGesture::Mode::TAP_DRAG: - case PointerGesture::Mode::BUTTON_CLICK_OR_DRAG: - case PointerGesture::Mode::HOVER: - case PointerGesture::Mode::PRESS: - case PointerGesture::Mode::SWIPE: - // Unfade the pointer when the current gesture manipulates the - // area directly under the pointer. - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - break; - case PointerGesture::Mode::FREEFORM: - // Fade the pointer when the current gesture manipulates a different - // area and there are spots to guide the user experience. - if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - } else { - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - } - break; - } - // Send events! int32_t metaState = getContext()->getGlobalMetaState(); int32_t buttonState = mCurrentCookedState.buttonState; @@ -2746,7 +2637,6 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns // the pointer is hovering again even if the user is not currently touching // the touch pad. This ensures that a view will receive a fresh hover enter // event after a tap. - const auto [x, y] = mPointerController->getPosition(); PointerProperties pointerProperties; pointerProperties.clear(); @@ -2755,16 +2645,12 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns PointerCoords pointerCoords; pointerCoords.clear(); - pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); - pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); - - const int32_t displayId = mPointerController->getDisplayId(); out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, displayId, policyFlags, + mSource, ADISPLAY_ID_NONE, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, - &pointerCoords, 0, 0, x, y, mPointerGesture.downTime, + &pointerCoords, 0, 0, 0.f, 0.f, mPointerGesture.downTime, /*videoFrames=*/{})); } @@ -2810,12 +2696,6 @@ std::list TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs // Reset the current pointer gesture. mPointerGesture.reset(); mPointerVelocityControl.reset(); - - // Remove any current spots. - if (mPointerController != nullptr) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - mPointerController->clearSpots(); - } return out; } @@ -2951,8 +2831,6 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerVelocityControl.reset(); } - const auto [x, y] = mPointerController->getPosition(); - mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG; mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); @@ -2961,8 +2839,6 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } else if (currentFingerCount == 0) { // Case 3. No fingers down and button is not pressed. (NEUTRAL) @@ -2977,9 +2853,8 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) && lastFingerCount == 1) { if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) { - const auto [x, y] = mPointerController->getPosition(); - if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && - fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { + if (fabs(0.f - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && + fabs(0.f - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP"); mPointerGesture.tapUpTime = when; @@ -3006,7 +2881,7 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi tapped = true; } else { ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP, deltaX=%f, deltaY=%f", - x - mPointerGesture.tapX, y - mPointerGesture.tapY); + 0.f - mPointerGesture.tapX, 0.f - mPointerGesture.tapY); } } else { if (DEBUG_GESTURES) { @@ -3038,13 +2913,12 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER; if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) { if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) { - const auto [x, y] = mPointerController->getPosition(); - if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && - fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { + if (fabs(0.f - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop && + fabs(0.f - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) { mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG; } else { ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", - x - mPointerGesture.tapX, y - mPointerGesture.tapY); + 0.f - mPointerGesture.tapX, 0.f - mPointerGesture.tapY); } } else { ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, %0.3fms time since up", @@ -3074,8 +2948,6 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi down = false; } - const auto [x, y] = mPointerController->getPosition(); - mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; @@ -3083,16 +2955,14 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId; mPointerGesture.currentGestureProperties[0].toolType = ToolType::FINGER; mPointerGesture.currentGestureCoords[0].clear(); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); - mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); if (lastFingerCount == 0 && currentFingerCount != 0) { mPointerGesture.resetTap(); mPointerGesture.tapDownTime = when; - mPointerGesture.tapX = x; - mPointerGesture.tapY = y; + mPointerGesture.tapX = 0.f; + mPointerGesture.tapY = 0.f; } } else { // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM) @@ -3243,8 +3113,8 @@ void TouchInputMapper::prepareMultiFingerPointerGestures(nsecs_t when, bool* can mCurrentRawState.rawPointerData .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX, &mPointerGesture.referenceTouchY); - std::tie(mPointerGesture.referenceGestureX, mPointerGesture.referenceGestureY) = - mPointerController->getPosition(); + mPointerGesture.referenceGestureX = 0.f; + mPointerGesture.referenceGestureY = 0.f; } // Clear the reference deltas for fingers not yet included in the reference calculation. @@ -3539,8 +3409,6 @@ void TouchInputMapper::moveMousePointerFromPointerDelta(nsecs_t when, uint32_t p rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY); mPointerVelocityControl.move(when, &deltaX, &deltaY); - - mPointerController->move(deltaX, deltaY); } std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime, @@ -3557,13 +3425,6 @@ std::list TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsec float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX(); float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY(); - // Styluses are configured specifically for one display. We only update the - // PointerController for this stylus if the PointerController is configured for - // the same display as this stylus, - if (getAssociatedDisplayId() == mViewport.displayId) { - mPointerController->setPosition(x, y); - std::tie(x, y) = mPointerController->getPosition(); - } mPointerSimple.currentCoords = mCurrentCookedState.cookedPointerData.pointerCoords[index]; mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); @@ -3601,12 +3462,9 @@ std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs down = isPointerDown(mCurrentRawState.buttonState); hovering = !down; - const auto [x, y] = mPointerController->getPosition(); const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id]; mPointerSimple.currentCoords = mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]; - mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x); - mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y); mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, hovering ? 0.0f : 1.0f); mPointerSimple.currentProperties.id = 0; @@ -3619,8 +3477,7 @@ std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs hovering = false; } - const int32_t displayId = mPointerController->getDisplayId(); - return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, displayId); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, ADISPLAY_ID_NONE); } std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, @@ -3641,17 +3498,6 @@ std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsec int32_t metaState = getContext()->getGlobalMetaState(); auto cursorPosition = mPointerSimple.currentCoords.getXYValue(); - if (displayId == mPointerController->getDisplayId()) { - std::tie(cursorPosition.x, cursorPosition.y) = mPointerController->getPosition(); - if (down || hovering) { - mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER); - mPointerController->clearSpots(); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - } - } - if (mPointerSimple.down && !down) { mPointerSimple.down = false; @@ -3787,9 +3633,6 @@ std::list TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t mPointerSimple.lastCursorX, mPointerSimple.lastCursorY, mPointerSimple.downTime, /*videoFrames=*/{})); - if (mPointerController != nullptr) { - mPointerController->fade(PointerControllerInterface::Transition::GRADUAL); - } } mPointerSimple.reset(); return out; @@ -3841,32 +3684,11 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( } const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); - const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled && - mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, pointerProperties) && - mPointerController && displayId != ADISPLAY_ID_NONE && - displayId == mPointerController->getDisplayId(); - if (showDirectStylusPointer) { - switch (action & AMOTION_EVENT_ACTION_MASK) { - case AMOTION_EVENT_ACTION_HOVER_ENTER: - case AMOTION_EVENT_ACTION_HOVER_MOVE: - mPointerController->setPresentation( - PointerControllerInterface::Presentation::STYLUS_HOVER); - mPointerController - ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(), - mCurrentCookedState.cookedPointerData.pointerCoords[0] - .getY()); - mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE); - break; - case AMOTION_EVENT_ACTION_HOVER_EXIT: - mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE); - break; - } - } float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mDeviceMode == DeviceMode::POINTER) { - std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition(); + xCursorPosition = yCursorPosition = 0.f; } const int32_t deviceId = getDeviceId(); std::vector frames = getDeviceContext().getVideoFrames(); @@ -4138,7 +3960,7 @@ bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, std::optional TouchInputMapper::getAssociatedDisplayId() { if (mParameters.hasAssociatedDisplay) { if (mDeviceMode == DeviceMode::POINTER) { - return std::make_optional(mPointerController->getDisplayId()); + return ADISPLAY_ID_NONE; } else { return std::make_optional(mViewport.displayId); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 9b7fe93d10..6485ab2159 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -46,7 +46,6 @@ #include "InputMapper.h" #include "InputReaderBase.h" #include "NotifyArgs.h" -#include "PointerControllerInterface.h" #include "StylusState.h" #include "TouchButtonAccumulator.h" @@ -392,9 +391,6 @@ protected: // The time the primary pointer last went down. nsecs_t mDownTime{0}; - // The pointer controller, or null if the device is not a pointer. - std::shared_ptr mPointerController; - std::vector mVirtualKeys; explicit TouchInputMapper(InputDeviceContext& deviceContext, @@ -837,9 +833,6 @@ private: // Returns if this touch device is a touch screen with an associated display. bool isTouchScreen(); - // Updates touch spots if they are enabled. Should only be used when this device is a - // touchscreen. - void updateTouchSpots(); bool isPointInsidePhysicalFrame(int32_t x, int32_t y) const; const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e5d929d690..e1b46faac6 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5344,10 +5344,6 @@ TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) { } TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) { - std::shared_ptr fakePointerController = - std::make_shared(); - mFakePolicy->setPointerController(fakePointerController); - addConfigurationProperty("touch.deviceType", "pointer"); prepareAxes(POSITION); prepareDisplay(ui::ROTATION_0); @@ -6192,52 +6188,6 @@ TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorr ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources()); } -TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) { - std::shared_ptr fakePointerController = - std::make_shared(); - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(ui::ROTATION_0); - prepareButtons(); - prepareAxes(POSITION); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerController(fakePointerController); - mFakePolicy->setStylusPointerIconEnabled(true); - SingleTouchInputMapper& mapper = constructAndAddMapper(); - - processKey(mapper, BTN_TOOL_PEN, 1); - processMove(mapper, 100, 200); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(ToolType::STYLUS), - WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); - ASSERT_TRUE(fakePointerController->isPointerShown()); - ASSERT_NO_FATAL_FAILURE( - fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200))); -} - -TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) { - std::shared_ptr fakePointerController = - std::make_shared(); - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(ui::ROTATION_0); - prepareButtons(); - prepareAxes(POSITION); - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerController(fakePointerController); - mFakePolicy->setStylusPointerIconEnabled(false); - SingleTouchInputMapper& mapper = constructAndAddMapper(); - - processKey(mapper, BTN_TOOL_PEN, 1); - processMove(mapper, 100, 200); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(ToolType::STYLUS), - WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); - ASSERT_FALSE(fakePointerController->isPointerShown()); -} - TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) { // Initialize the device without setting device source to touch navigation. addConfigurationProperty("touch.deviceType", "touchScreen"); @@ -8717,21 +8667,12 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) { } TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { - // Setup for second display. - std::shared_ptr fakePointerController = - std::make_shared(); - fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - fakePointerController->setPosition(100, 200); - mFakePolicy->setPointerController(fakePointerController); - - mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID); prepareSecondaryDisplay(ViewportType::EXTERNAL); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); MultiTouchInputMapper& mapper = constructAndAddMapper(); - // Check source is mouse that would obtain the PointerController. ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources()); NotifyMotionArgs motionArgs; @@ -8740,7 +8681,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId); + ASSERT_EQ(ADISPLAY_ID_NONE, motionArgs.displayId); } /** @@ -8920,97 +8861,6 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_TouchesNotAborted) WithMotionAction(AMOTION_EVENT_ACTION_MOVE))); } -TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) { - // Setup the first touch screen device. - prepareAxes(POSITION | ID | SLOT); - addConfigurationProperty("touch.deviceType", "touchScreen"); - MultiTouchInputMapper& mapper = constructAndAddMapper(); - - // Create the second touch screen device, and enable multi fingers. - const std::string USB2 = "USB2"; - const std::string DEVICE_NAME2 = "TOUCHSCREEN2"; - constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1; - constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1; - std::shared_ptr device2 = - newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID, - ftl::Flags(0)); - - mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, - /*flat=*/0, /*fuzz=*/0); - mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, - /*flat=*/0, /*fuzz=*/0); - mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX, - /*flat=*/0, /*fuzz=*/0); - mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX, - /*flat=*/0, /*fuzz=*/0); - mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, /*value=*/0); - mFakeEventHub->addConfigurationProperty(SECOND_EVENTHUB_ID, String8("touch.deviceType"), - String8("touchScreen")); - - // Setup the second touch screen device. - device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); - MultiTouchInputMapper& mapper2 = device2->constructAndAddMapper< - MultiTouchInputMapper>(SECOND_EVENTHUB_ID, mFakePolicy->getReaderConfiguration()); - std::list unused = - device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - /*changes=*/{}); - unused += device2->reset(ARBITRARY_TIME); - - // Setup PointerController. - std::shared_ptr fakePointerController = - std::make_shared(); - mFakePolicy->setPointerController(fakePointerController); - - // Setup policy for associated displays and show touches. - const uint8_t hdmi1 = 0; - const uint8_t hdmi2 = 1; - mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1); - mFakePolicy->addInputPortAssociation(USB2, hdmi2); - mFakePolicy->setShowTouches(true); - - // Create displays. - prepareDisplay(ui::ROTATION_0, hdmi1); - prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2); - - // Default device will reconfigure above, need additional reconfiguration for another device. - unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::Change::DISPLAY_INFO | - InputReaderConfiguration::Change::SHOW_TOUCHES); - - // Two fingers down at default display. - int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500; - processPosition(mapper, x1, y1); - processId(mapper, 1); - processSlot(mapper, 1); - processPosition(mapper, x2, y2); - processId(mapper, 2); - processSync(mapper); - - std::map>::const_iterator iter = - fakePointerController->getSpots().find(DISPLAY_ID); - ASSERT_TRUE(iter != fakePointerController->getSpots().end()); - ASSERT_EQ(size_t(2), iter->second.size()); - - // Two fingers down at second display. - processPosition(mapper2, x1, y1); - processId(mapper2, 1); - processSlot(mapper2, 1); - processPosition(mapper2, x2, y2); - processId(mapper2, 2); - processSync(mapper2); - - iter = fakePointerController->getSpots().find(SECONDARY_DISPLAY_ID); - ASSERT_TRUE(iter != fakePointerController->getSpots().end()); - ASSERT_EQ(size_t(2), iter->second.size()); - - // Disable the show touches configuration and ensure the spots are cleared. - mFakePolicy->setShowTouches(false); - unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), - InputReaderConfiguration::Change::SHOW_TOUCHES); - - ASSERT_TRUE(fakePointerController->getSpots().empty()); -} - TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) { prepareAxes(POSITION); addConfigurationProperty("touch.deviceType", "touchScreen"); @@ -9703,58 +9553,6 @@ TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) { WithToolType(ToolType::STYLUS)))); } -TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(ui::ROTATION_0); - prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE); - // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only - // indicate stylus presence dynamically. - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - std::shared_ptr fakePointerController = - std::make_shared(); - mFakePolicy->setPointerController(fakePointerController); - mFakePolicy->setStylusPointerIconEnabled(true); - MultiTouchInputMapper& mapper = constructAndAddMapper(); - - processId(mapper, FIRST_TRACKING_ID); - processPressure(mapper, RAW_PRESSURE_MIN); - processPosition(mapper, 100, 200); - processToolType(mapper, MT_TOOL_PEN); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(ToolType::STYLUS), - WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); - ASSERT_TRUE(fakePointerController->isPointerShown()); - ASSERT_NO_FATAL_FAILURE( - fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200))); -} - -TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) { - addConfigurationProperty("touch.deviceType", "touchScreen"); - prepareDisplay(ui::ROTATION_0); - prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE); - // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only - // indicate stylus presence dynamically. - mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0); - std::shared_ptr fakePointerController = - std::make_shared(); - mFakePolicy->setPointerController(fakePointerController); - mFakePolicy->setStylusPointerIconEnabled(false); - MultiTouchInputMapper& mapper = constructAndAddMapper(); - - processId(mapper, FIRST_TRACKING_ID); - processPressure(mapper, RAW_PRESSURE_MIN); - processPosition(mapper, 100, 200); - processToolType(mapper, MT_TOOL_PEN); - processSync(mapper); - ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled( - AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), - WithToolType(ToolType::STYLUS), - WithPointerCoords(0, toDisplayX(100), toDisplayY(200))))); - ASSERT_FALSE(fakePointerController->isPointerShown()); -} - // --- MultiTouchInputMapperTest_ExternalDevice --- class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest { @@ -9790,18 +9588,15 @@ TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId); } -TEST_F(MultiTouchInputMapperTest, Process_TouchpadPointer) { - std::shared_ptr fakePointerController = - std::make_shared(); - fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - fakePointerController->setPosition(0, 0); - +// TODO(b/281840344): Remove the test when the old touchpad stack is removed. It is currently +// unclear what the behavior of the touchpad logic in TouchInputMapper should do after the +// PointerChoreographer refactor. +TEST_F(MultiTouchInputMapperTest, DISABLED_Process_TouchpadPointer) { // prepare device prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerController(fakePointerController); MultiTouchInputMapper& mapper = constructAndAddMapper(); // run uncaptured pointer tests - pushes out generic events // FINGER 0 DOWN @@ -9855,13 +9650,9 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadPointer) { } TEST_F(MultiTouchInputMapperTest, Touchpad_GetSources) { - std::shared_ptr fakePointerController = - std::make_shared(); - prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION | ID | SLOT); mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0); - mFakePolicy->setPointerController(fakePointerController); mFakePolicy->setPointerCapture(/*window=*/nullptr); MultiTouchInputMapper& mapper = constructAndAddMapper(); @@ -9925,10 +9716,6 @@ protected: float mPointerXZoomScale; void preparePointerMode(int xAxisResolution, int yAxisResolution) { addConfigurationProperty("touch.deviceType", "pointer"); - std::shared_ptr fakePointerController = - std::make_shared(); - fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1); - fakePointerController->setPosition(0, 0); prepareDisplay(ui::ROTATION_0); prepareAxes(POSITION); @@ -9937,7 +9724,6 @@ protected: // needs to be disabled, and the pointer gesture needs to be enabled. mFakePolicy->setPointerCapture(/*window=*/nullptr); mFakePolicy->setPointerGestureEnabled(true); - mFakePolicy->setPointerController(fakePointerController); float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN); float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT); -- GitLab From 41ade202bb683797beaa64e3459a69952ce1f0bc Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Fri, 3 May 2024 01:25:50 +0000 Subject: [PATCH 308/465] Wrap RenderArea creation in a builder pattern Use a builder to pass around parameters used for RenderArea creation. This allows more flexibility for when the RenderArea is created, which aids in the overall goal of reducing the number of SF main thread hops during screenshots. Creating a builder will allow the render area to later be passed into captureScreenCommon() without being wrapped in a future. Bug: b/294936197 Test: atest SurfaceFlinger_test Change-Id: I9545e02af42c7e6cd9b0c328e2ecce995811f2d7 --- .../surfaceflinger/RegionSamplingThread.cpp | 8 +- services/surfaceflinger/RenderAreaBuilder.h | 114 ++++++++++++++++++ services/surfaceflinger/SurfaceFlinger.cpp | 47 ++++---- 3 files changed, 144 insertions(+), 25 deletions(-) create mode 100644 services/surfaceflinger/RenderAreaBuilder.h diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 77e045d6f9..2ec20ad5c2 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -42,6 +42,7 @@ #include "DisplayRenderArea.h" #include "FrontEnd/LayerCreationArgs.h" #include "Layer.h" +#include "RenderAreaBuilder.h" #include "Scheduler/VsyncController.h" #include "SurfaceFlinger.h" @@ -279,8 +280,11 @@ void RegionSamplingThread::captureSample() { constexpr bool kHintForSeamlessTransition = false; SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(), - ui::Dataspace::V0_SRGB, kHintForSeamlessTransition); + DisplayRenderAreaBuilder displayRenderArea(sampledBounds, sampledBounds.getSize(), + ui::Dataspace::V0_SRGB, + kHintForSeamlessTransition, + true /* captureSecureLayers */, displayWeak); + return displayRenderArea.build(); }); std::unordered_set, SpHash> listeners; diff --git a/services/surfaceflinger/RenderAreaBuilder.h b/services/surfaceflinger/RenderAreaBuilder.h new file mode 100644 index 0000000000..012acd2a67 --- /dev/null +++ b/services/surfaceflinger/RenderAreaBuilder.h @@ -0,0 +1,114 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "DisplayDevice.h" +#include "DisplayRenderArea.h" +#include "LayerRenderArea.h" +#include "ui/Size.h" +#include "ui/Transform.h" + +namespace android { +/** + * A parameter object for creating a render area + */ +struct RenderAreaBuilder { + // Source crop of the render area + Rect crop; + + // Size of the physical render area + ui::Size reqSize; + + // Composition data space of the render area + ui::Dataspace reqDataSpace; + + // If true, the secure layer would be blacked out or skipped + // when rendered to an insecure render area + bool allowSecureLayers; + + // If true, the render result may be used for system animations + // that must preserve the exact colors of the display + bool hintForSeamlessTransition; + + virtual std::unique_ptr build() const = 0; + + RenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, + bool allowSecureLayers, bool hintForSeamlessTransition) + : crop(crop), + reqSize(reqSize), + reqDataSpace(reqDataSpace), + allowSecureLayers(allowSecureLayers), + hintForSeamlessTransition(hintForSeamlessTransition) {} + + virtual ~RenderAreaBuilder() = default; +}; + +struct DisplayRenderAreaBuilder : RenderAreaBuilder { + DisplayRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, + bool allowSecureLayers, bool hintForSeamlessTransition, + wp displayWeak) + : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers, + hintForSeamlessTransition), + displayWeak(displayWeak) {} + + // Display that render area will be on + wp displayWeak; + + std::unique_ptr build() const override { + return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace, + hintForSeamlessTransition, allowSecureLayers); + } +}; + +struct LayerRenderAreaBuilder : RenderAreaBuilder { + LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, + bool allowSecureLayers, bool hintForSeamlessTransition, sp layer, + bool childrenOnly) + : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers, + hintForSeamlessTransition), + layer(layer), + childrenOnly(childrenOnly) {} + + // Layer that the render area will be on + sp layer; + + // Transform to be applied on the layers to transform them + // into the logical render area + ui::Transform layerTransform{ui::Transform()}; + + // Buffer bounds + Rect layerBufferSize{Rect()}; + + // If false, transform is inverted from the parent snapshot + bool childrenOnly; + + // Uses parent snapshot to determine layer transform and buffer size + void setLayerInfo(const frontend::LayerSnapshot* parentSnapshot) { + if (!childrenOnly) { + layerTransform = parentSnapshot->localTransform.inverse(); + } + layerBufferSize = parentSnapshot->bufferSize; + } + + std::unique_ptr build() const override { + return std::make_unique(layer, crop, reqSize, reqDataSpace, + allowSecureLayers, layerTransform, layerBufferSize, + hintForSeamlessTransition); + } +}; + +} // namespace android \ No newline at end of file diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index df226c94ff..5263aa80b9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -148,6 +148,7 @@ #include "MutexUtils.h" #include "NativeWindowSurface.h" #include "RegionSamplingThread.h" +#include "RenderAreaBuilder.h" #include "Scheduler/EventThread.h" #include "Scheduler/LayerHistory.h" #include "Scheduler/Scheduler.h" @@ -7918,8 +7919,10 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace, - args.hintForSeamlessTransition, args.captureSecureLayers); + DisplayRenderAreaBuilder displayRenderArea(args.sourceCrop, reqSize, args.dataspace, + args.hintForSeamlessTransition, + args.captureSecureLayers, displayWeak); + return displayRenderArea.build(); }); GetLayerSnapshotsFunction getLayerSnapshots; @@ -7972,9 +7975,10 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args } RenderAreaFuture renderAreaFuture = ftl::defer([=] { - return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace, - args.hintForSeamlessTransition, - false /* captureSecureLayers */); + DisplayRenderAreaBuilder displayRenderArea(Rect(), size, args.dataspace, + args.hintForSeamlessTransition, + false /* captureSecureLayers */, displayWeak); + return displayRenderArea.build(); }); GetLayerSnapshotsFunction getLayerSnapshots; @@ -8079,25 +8083,22 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return; } - RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() FTL_FAKE_GUARD(kMainThreadContext) - -> std::unique_ptr { - ui::Transform layerTransform; - Rect layerBufferSize; - frontend::LayerSnapshot* snapshot = - mLayerSnapshotBuilder.getSnapshot(parent->getSequence()); - if (!snapshot) { - ALOGW("Couldn't find layer snapshot for %d", parent->getSequence()); - } else { - if (!args.childrenOnly) { - layerTransform = snapshot->localTransform.inverse(); - } - layerBufferSize = snapshot->bufferSize; - } + RenderAreaFuture renderAreaFuture = ftl::defer( + [=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> std::unique_ptr { + LayerRenderAreaBuilder layerRenderArea(crop, reqSize, dataspace, + args.captureSecureLayers, + args.hintForSeamlessTransition, parent, + args.childrenOnly); - return std::make_unique(parent, crop, reqSize, dataspace, - args.captureSecureLayers, layerTransform, - layerBufferSize, args.hintForSeamlessTransition); - }); + frontend::LayerSnapshot* snapshot = + mLayerSnapshotBuilder.getSnapshot(parent->getSequence()); + if (!snapshot) { + ALOGW("Couldn't find layer snapshot for %d", parent->getSequence()); + } else { + layerRenderArea.setLayerInfo(snapshot); + } + return layerRenderArea.build(); + }); GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { std::optional parentCrop = std::nullopt; -- GitLab From be95e02cb0e49026691a299be2a97030a5654553 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Tue, 7 May 2024 07:04:55 +0000 Subject: [PATCH 309/465] Call SF#processDisplayChangesLocked in testable SF processDisplayChangesLocked updates rotations and should be performed in testable SF's commitTransactionsLocked. Several tests involving rotations had broken after the legacy frontend flag was removed because these rotations were not being updated. SF already calls processDisplayChangesLocked in correct locations, but some test suites neglect to do so. These changes are therefore isolated to just the testable SF API. Bug: b/330785038 Test: atest ActiveDispayRotationFlagsTest Test: atest DisplayModeSwitchingTest Test: atest DisplayTransactionCommitTest Test: presubmit Change-Id: I9ca7bc70b9a5a462e623a788f7a98a738c6a3482 --- services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h | 1 + 1 file changed, 1 insertion(+) diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 85b17176fa..85da422b17 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -448,6 +448,7 @@ public: void commitTransactionsLocked(uint32_t transactionFlags) { Mutex::Autolock lock(mFlinger->mStateLock); ftl::FakeGuard guard(kMainThreadContext); + mFlinger->processDisplayChangesLocked(); mFlinger->commitTransactionsLocked(transactionFlags); } -- GitLab From 03a0d2d895d5bf1767bc75220a20bbedebccfe8d Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 7 May 2024 15:07:06 +0000 Subject: [PATCH 310/465] Follow up on mouse cleanup after PointerChoreographer refactor Addressing additional comments from change: I4be21303d0c809a9e8e14854faf213a0d41fddf1 Bug: 311416205 Change-Id: I4b259ca8d5ba4d94fbae8e22ae79e1411621027e Test: presubmit --- .../reader/mapper/CursorInputMapper.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index becac5a671..ede2d72c44 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -507,7 +507,6 @@ void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfigurat mDisplayId = ADISPLAY_ID_NONE; std::optional resolvedViewport; - bool isBoundsSet = false; if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) { // This InputDevice is associated with a viewport. // Only generate events for the associated display. @@ -526,14 +525,12 @@ void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfigurat ? ui::ROTATION_0 : getInverseRotation(resolvedViewport->orientation); - if (!isBoundsSet) { - mBoundsInLogicalDisplay = resolvedViewport - ? FloatRect{static_cast(resolvedViewport->logicalLeft), - static_cast(resolvedViewport->logicalTop), - static_cast(resolvedViewport->logicalRight - 1), - static_cast(resolvedViewport->logicalBottom - 1)} - : FloatRect{0, 0, 0, 0}; - } + mBoundsInLogicalDisplay = resolvedViewport + ? FloatRect{static_cast(resolvedViewport->logicalLeft), + static_cast(resolvedViewport->logicalTop), + static_cast(resolvedViewport->logicalRight - 1), + static_cast(resolvedViewport->logicalBottom - 1)} + : FloatRect{0, 0, 0, 0}; bumpGeneration(); } -- GitLab From c23fcd04886477f1c8026948ed01f81bca082699 Mon Sep 17 00:00:00 2001 From: Tom Murphy Date: Wed, 13 Mar 2024 10:22:06 +0000 Subject: [PATCH 311/465] Add engine name to GpuStatsAppInfo The engine name from VkApplicationInfo is useful for collection metrics on Vulkan usage. Add it to the collected metrics in GpuStatsAppInfo. Bug: 330118952 Test: adb shell dumpsys gpu Test: atest GpuStatsTest Change-Id: If4096b8a96ed77ddb1d2fd9f48c2b8825b3d0280 --- libs/graphicsenv/GpuStatsInfo.cpp | 7 ++ libs/graphicsenv/GraphicsEnv.cpp | 15 ++++ libs/graphicsenv/IGpuService.cpp | 28 ++++++++ .../include/graphicsenv/GpuStatsInfo.h | 5 ++ .../include/graphicsenv/GraphicsEnv.h | 2 + .../include/graphicsenv/IGpuService.h | 3 + services/gpuservice/GpuService.cpp | 6 ++ services/gpuservice/gpustats/GpuStats.cpp | 35 ++++++++- .../gpustats/include/gpustats/GpuStats.h | 3 + .../include/gpuservice/GpuService.h | 2 + .../tests/unittests/GpuStatsTest.cpp | 71 +++++++++++++++++++ vulkan/libvulkan/driver.cpp | 5 ++ 12 files changed, 181 insertions(+), 1 deletion(-) diff --git a/libs/graphicsenv/GpuStatsInfo.cpp b/libs/graphicsenv/GpuStatsInfo.cpp index 7b7421424d..33cebe37f5 100644 --- a/libs/graphicsenv/GpuStatsInfo.cpp +++ b/libs/graphicsenv/GpuStatsInfo.cpp @@ -96,6 +96,7 @@ status_t GpuStatsAppInfo::writeToParcel(Parcel* parcel) const { if ((status = parcel->writeUint64(vulkanDeviceFeaturesEnabled)) != OK) return status; if ((status = parcel->writeInt32Vector(vulkanInstanceExtensions)) != OK) return status; if ((status = parcel->writeInt32Vector(vulkanDeviceExtensions)) != OK) return status; + if ((status = parcel->writeUtf8VectorAsUtf16Vector(vulkanEngineNames)) != OK) return status; return OK; } @@ -118,6 +119,7 @@ status_t GpuStatsAppInfo::readFromParcel(const Parcel* parcel) { if ((status = parcel->readUint64(&vulkanDeviceFeaturesEnabled)) != OK) return status; if ((status = parcel->readInt32Vector(&vulkanInstanceExtensions)) != OK) return status; if ((status = parcel->readInt32Vector(&vulkanDeviceExtensions)) != OK) return status; + if ((status = parcel->readUtf8VectorFromUtf16Vector(&vulkanEngineNames)) != OK) return status; return OK; } @@ -161,6 +163,11 @@ std::string GpuStatsAppInfo::toString() const { StringAppendF(&result, " 0x%x", extension); } result.append("\n"); + result.append("vulkanEngineNames:"); + for (const std::string& engineName : vulkanEngineNames) { + StringAppendF(&result, " %s,", engineName.c_str()); + } + result.append("\n"); return result; } diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp index 50c05f4e5b..52383acb34 100644 --- a/libs/graphicsenv/GraphicsEnv.cpp +++ b/libs/graphicsenv/GraphicsEnv.cpp @@ -445,6 +445,21 @@ void GraphicsEnv::setVulkanDeviceExtensions(uint32_t enabledExtensionCount, extensionHashes, numStats); } +void GraphicsEnv::addVulkanEngineName(const char* engineName) { + ATRACE_CALL(); + if (engineName == nullptr) { + return; + } + std::lock_guard lock(mStatsLock); + if (!readyToSendGpuStatsLocked()) return; + + const sp gpuService = getGpuService(); + if (gpuService) { + gpuService->addVulkanEngineName(mGpuStats.appPackageName, mGpuStats.driverVersionCode, + engineName); + } +} + bool GraphicsEnv::readyToSendGpuStatsLocked() { // Only send stats for processes having at least one activity launched and that process doesn't // skip the GraphicsEnvironment setup. diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp index 5dc195c438..42e7c378a9 100644 --- a/libs/graphicsenv/IGpuService.cpp +++ b/libs/graphicsenv/IGpuService.cpp @@ -77,6 +77,19 @@ public: IBinder::FLAG_ONEWAY); } + void addVulkanEngineName(const std::string& appPackageName, const uint64_t driverVersionCode, + const char* engineName) override { + Parcel data, reply; + data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); + + data.writeUtf8AsUtf16(appPackageName); + data.writeUint64(driverVersionCode); + data.writeCString(engineName); + + remote()->transact(BnGpuService::ADD_VULKAN_ENGINE_NAME, data, &reply, + IBinder::FLAG_ONEWAY); + } + void setUpdatableDriverPath(const std::string& driverPath) override { Parcel data, reply; data.writeInterfaceToken(IGpuService::getInterfaceDescriptor()); @@ -197,6 +210,21 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep return OK; } + case ADD_VULKAN_ENGINE_NAME: { + CHECK_INTERFACE(IGpuService, data, reply); + + std::string appPackageName; + if ((status = data.readUtf8FromUtf16(&appPackageName)) != OK) return status; + + uint64_t driverVersionCode; + if ((status = data.readUint64(&driverVersionCode)) != OK) return status; + + const char* engineName; + if ((engineName = data.readCString()) == nullptr) return BAD_VALUE; + + addVulkanEngineName(appPackageName, driverVersionCode, engineName); + return OK; + } case SET_UPDATABLE_DRIVER_PATH: { CHECK_INTERFACE(IGpuService, data, reply); diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h index 9ebaf16eb4..23f583bda0 100644 --- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h +++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h @@ -60,6 +60,10 @@ class GpuStatsAppInfo : public Parcelable { public: // This limits the worst case number of extensions to be tracked. static const uint32_t MAX_NUM_EXTENSIONS = 100; + // Max number of vulkan engine names for a single GpuStatsAppInfo + static const uint32_t MAX_VULKAN_ENGINE_NAMES = 16; + // Max length of a vulkan engine name string + static const size_t MAX_VULKAN_ENGINE_NAME_LENGTH = 50; GpuStatsAppInfo() = default; GpuStatsAppInfo(const GpuStatsAppInfo&) = default; @@ -84,6 +88,7 @@ public: uint64_t vulkanDeviceFeaturesEnabled = 0; std::vector vulkanInstanceExtensions = {}; std::vector vulkanDeviceExtensions = {}; + std::vector vulkanEngineNames = {}; std::chrono::time_point lastAccessTime; }; diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h index 6cce3f6998..b0ab0b9d22 100644 --- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h +++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h @@ -89,6 +89,8 @@ public: // Set which device extensions are enabled for the app. void setVulkanDeviceExtensions(uint32_t enabledExtensionCount, const char* const* ppEnabledExtensionNames); + // Add the engine name passed in VkApplicationInfo during CreateInstance + void addVulkanEngineName(const char* engineName); /* * Api for Vk/GL layer injection. Presently, drivers enable certain diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h index 45f05d6555..a0d6e37302 100644 --- a/libs/graphicsenv/include/graphicsenv/IGpuService.h +++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h @@ -46,6 +46,8 @@ public: const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t* values, const uint32_t valueCount) = 0; + virtual void addVulkanEngineName(const std::string& appPackageName, + const uint64_t driverVersionCode, const char* engineName) = 0; // setter and getter for updatable driver path. virtual void setUpdatableDriverPath(const std::string& driverPath) = 0; @@ -64,6 +66,7 @@ public: GET_UPDATABLE_DRIVER_PATH, TOGGLE_ANGLE_AS_SYSTEM_DRIVER, SET_TARGET_STATS_ARRAY, + ADD_VULKAN_ENGINE_NAME, // Always append new enum to the end. }; diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp index a481c628aa..fadb1fd426 100644 --- a/services/gpuservice/GpuService.cpp +++ b/services/gpuservice/GpuService.cpp @@ -100,6 +100,12 @@ void GpuService::setTargetStatsArray(const std::string& appPackageName, mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount); } +void GpuService::addVulkanEngineName(const std::string& appPackageName, + const uint64_t driverVersionCode, + const char* engineName) { + mGpuStats->addVulkanEngineName(appPackageName, driverVersionCode, engineName); +} + void GpuService::toggleAngleAsSystemDriver(bool enabled) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp index 11b636d564..6d758bc8a8 100644 --- a/services/gpuservice/gpustats/GpuStats.cpp +++ b/services/gpuservice/gpustats/GpuStats.cpp @@ -181,6 +181,33 @@ void GpuStats::insertTargetStats(const std::string& appPackageName, return insertTargetStatsArray(appPackageName, driverVersionCode, stats, &value, 1); } +void GpuStats::addVulkanEngineName(const std::string& appPackageName, + const uint64_t driverVersionCode, + const char* engineNameCStr) { + ATRACE_CALL(); + + const std::string appStatsKey = appPackageName + std::to_string(driverVersionCode); + const size_t engineNameLen = std::min(strlen(engineNameCStr), + GpuStatsAppInfo::MAX_VULKAN_ENGINE_NAME_LENGTH); + const std::string engineName{engineNameCStr, engineNameLen}; + + std::lock_guard lock(mLock); + registerStatsdCallbacksIfNeeded(); + + const auto foundApp = mAppStats.find(appStatsKey); + if (foundApp == mAppStats.end()) { + return; + } + + // Storing in std::set<> is not efficient for serialization tasks. Use + // vector instead and filter out dups + std::vector& engineNames = foundApp->second.vulkanEngineNames; + if (engineNames.size() < GpuStatsAppInfo::MAX_VULKAN_ENGINE_NAMES + && std::find(engineNames.cbegin(), engineNames.cend(), engineName) == engineNames.cend()) { + engineNames.push_back(engineName); + } +} + void GpuStats::insertTargetStatsArray(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t* values, const uint32_t valueCount) { @@ -389,6 +416,11 @@ AStatsManager_PullAtomCallbackReturn GpuStats::pullAppInfoAtom(AStatsEventList* std::string angleDriverBytes = int64VectorToProtoByteString( ele.second.angleDriverLoadingTime); + std::vector engineNames; + for (const std::string &engineName : ele.second.vulkanEngineNames) { + engineNames.push_back(engineName.c_str()); + } + android::util::addAStatsEvent( data, android::util::GPU_STATS_APP_INFO, @@ -410,7 +442,8 @@ AStatsManager_PullAtomCallbackReturn GpuStats::pullAppInfoAtom(AStatsEventList* ele.second.vulkanApiVersion, ele.second.vulkanDeviceFeaturesEnabled, ele.second.vulkanInstanceExtensions, - ele.second.vulkanDeviceExtensions); + ele.second.vulkanDeviceExtensions, + engineNames); } } diff --git a/services/gpuservice/gpustats/include/gpustats/GpuStats.h b/services/gpuservice/gpustats/include/gpustats/GpuStats.h index 22c64dbc02..961f011a72 100644 --- a/services/gpuservice/gpustats/include/gpustats/GpuStats.h +++ b/services/gpuservice/gpustats/include/gpustats/GpuStats.h @@ -44,6 +44,9 @@ public: void insertTargetStatsArray(const std::string& appPackageName, const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats, const uint64_t* values, const uint32_t valueCount); + // Add the engine name passed in VkApplicationInfo during CreateInstance + void addVulkanEngineName(const std::string& appPackageName, + const uint64_t driverVersionCode, const char* engineName); // dumpsys interface void dump(const Vector& args, std::string* result); diff --git a/services/gpuservice/include/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h index 54f8f666bc..3072885838 100644 --- a/services/gpuservice/include/gpuservice/GpuService.h +++ b/services/gpuservice/include/gpuservice/GpuService.h @@ -64,6 +64,8 @@ private: void setUpdatableDriverPath(const std::string& driverPath) override; std::string getUpdatableDriverPath() override; void toggleAngleAsSystemDriver(bool enabled) override; + void addVulkanEngineName(const std::string& appPackageName, const uint64_t driverVersionCode, + const char *engineName) override; /* * IBinder interface diff --git a/services/gpuservice/tests/unittests/GpuStatsTest.cpp b/services/gpuservice/tests/unittests/GpuStatsTest.cpp index 4ce533ff7c..b367457579 100644 --- a/services/gpuservice/tests/unittests/GpuStatsTest.cpp +++ b/services/gpuservice/tests/unittests/GpuStatsTest.cpp @@ -46,6 +46,8 @@ using testing::HasSubstr; #define UPDATED_DRIVER_VER_CODE 1 #define UPDATED_DRIVER_BUILD_TIME 234 #define VULKAN_VERSION 345 +#define VULKAN_ENGINE_NAME_1 "testVulkanEngine1" +#define VULKAN_ENGINE_NAME_2 "testVulkanEngine2" #define APP_PKG_NAME_1 "testapp1" #define APP_PKG_NAME_2 "testapp2" #define DRIVER_LOADING_TIME_1 678 @@ -243,6 +245,8 @@ TEST_F(GpuStatsTest, canNotInsertTargetStatsBeforeProperSetup) { mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, VULKAN_DEVICE_EXTENSION_1); + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_1); EXPECT_TRUE(inputCommand(InputCommand::DUMP_APP).empty()); } @@ -282,6 +286,8 @@ TEST_F(GpuStatsTest, canInsertTargetStatsAfterProperSetup) { mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, VULKAN_DEVICE_EXTENSION_2); + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_1); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1")); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1")); @@ -302,6 +308,64 @@ TEST_F(GpuStatsTest, canInsertTargetStatsAfterProperSetup) { expectedResult.str(""); expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1 << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2; + expectedResult.str(""); + expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ","; + + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); +} + +// Verify the vulkanEngineNames list behaves like a set and dedupes additions +TEST_F(GpuStatsTest, vulkanEngineNamesBehavesLikeSet) { + mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME, + BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1, + VULKAN_VERSION, GpuStatsInfo::Driver::GL, true, + DRIVER_LOADING_TIME_1); + for (int i = 0; i < 4; i++) { + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_1); + } + + std::stringstream wrongResult, expectedResult; + wrongResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", " << + VULKAN_ENGINE_NAME_1; + expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1; + + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), Not(HasSubstr(wrongResult.str()))); + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); +} + +TEST_F(GpuStatsTest, vulkanEngineNamesCheckEmptyEngineNameAlone) { + mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME, + BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1, + VULKAN_VERSION, GpuStatsInfo::Driver::GL, true, + DRIVER_LOADING_TIME_1); + + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + ""); + + std::stringstream expectedResult; + expectedResult << "vulkanEngineNames: ,"; + + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); +} + +TEST_F(GpuStatsTest, vulkanEngineNamesCheckEmptyEngineNameWithOthers) { + mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME, + BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1, + VULKAN_VERSION, GpuStatsInfo::Driver::GL, true, + DRIVER_LOADING_TIME_1); + + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_1); + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + ""); + mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_2); + + std::stringstream expectedResult; + expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", " + << ", " << VULKAN_ENGINE_NAME_2; + EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); } @@ -350,6 +414,10 @@ TEST_F(GpuStatsTest, canInsertMoreThanMaxNumAppRecords) { mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE, GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION, VULKAN_DEVICE_EXTENSION_2); + mGpuStats->addVulkanEngineName(fullPkgName, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_1); + mGpuStats->addVulkanEngineName(fullPkgName, BUILTIN_DRIVER_VER_CODE, + VULKAN_ENGINE_NAME_2); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(fullPkgName.c_str())); EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1")); @@ -371,6 +439,9 @@ TEST_F(GpuStatsTest, canInsertMoreThanMaxNumAppRecords) { expectedResult.str(""); expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1 << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2; + expectedResult.str(""); + expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", " + << VULKAN_ENGINE_NAME_2 << ","; EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str())); } diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 81fd1185b6..7ea98f5469 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -1372,6 +1372,11 @@ VkResult CreateInstance(const VkInstanceCreateInfo* pCreateInfo, android::GraphicsEnv::getInstance().setTargetStats( android::GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION, vulkanApiVersion); + + if (pCreateInfo->pApplicationInfo->pEngineName) { + android::GraphicsEnv::getInstance().addVulkanEngineName( + pCreateInfo->pApplicationInfo->pEngineName); + } } // Update stats for the extensions requested -- GitLab From 4b6ad2d04e4191edcbf6aa74ff264e3bbcc37f18 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 4 Apr 2024 11:54:20 +0000 Subject: [PATCH 312/465] Add method to mark displays secure to hide pointer indicators In this CL adds a method to mark some displays secure, it will be used to allow pointer controller to hide touch indicators from the mirrored displays. Test: atest PointerChoreographerTest Bug: 325252005 Change-Id: Iafe7fbc62de39f9fc40119238d7404b97068516a --- .../inputflinger/PointerChoreographer.cpp | 100 +++++++++++++++++- services/inputflinger/PointerChoreographer.h | 22 +++- .../include/PointerControllerInterface.h | 5 + .../tests/FakePointerController.cpp | 16 +++ .../tests/FakePointerController.h | 4 + .../tests/PointerChoreographer_test.cpp | 75 ++++++++++++- .../tests/fuzzers/MapperHelpers.h | 1 + 7 files changed, 220 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 4dc273792c..a3d0c2b488 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -17,7 +17,12 @@ #define LOG_TAG "PointerChoreographer" #include +#include +#if defined(__ANDROID__) +#include +#endif #include +#include #include "PointerChoreographer.h" @@ -25,6 +30,10 @@ namespace android { +namespace input_flags = com::android::input::flags; +static const bool HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS = + input_flags::hide_pointer_indicators_for_secure_windows(); + namespace { bool isFromMouse(const NotifyMotionArgs& args) { @@ -96,6 +105,14 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, mShowTouchesEnabled(false), mStylusPointerIconEnabled(false) {} +PointerChoreographer::~PointerChoreographer() { + std::scoped_lock _l(mLock); + if (mWindowInfoListener == nullptr) { + return; + } + mWindowInfoListener->onPointerChoreographerDestroyed(); +} + void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { PointerDisplayChange pointerDisplayChange; @@ -231,6 +248,7 @@ void PointerChoreographer::processDrawingTabletEventLocked(const android::Notify auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId, getMouseControllerConstructor( args.displayId)); + // TODO (b/325252005): Add handing for drawing tablets mouse pointer controller PointerControllerInterface& pc = *it->second; @@ -268,7 +286,11 @@ void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMo } // Get the touch pointer controller for the device, or create one if it doesn't exist. - auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor); + auto [it, controllerAdded] = + mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor); + if (controllerAdded) { + onControllerAddedOrRemoved(); + } PointerControllerInterface& pc = *it->second; @@ -306,6 +328,7 @@ void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& auto [it, _] = mStylusPointersByDevice.try_emplace(args.deviceId, getStylusControllerConstructor(args.displayId)); + // TODO (b/325252005): Add handing for stylus pointer controller PointerControllerInterface& pc = *it->second; @@ -345,6 +368,31 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) mTouchPointersByDevice.erase(args.deviceId); mStylusPointersByDevice.erase(args.deviceId); mDrawingTabletPointersByDevice.erase(args.deviceId); + onControllerAddedOrRemoved(); +} + +void PointerChoreographer::onControllerAddedOrRemoved() { + if (!HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS) { + return; + } + bool requireListener = !mTouchPointersByDevice.empty(); + // TODO (b/325252005): Update for other types of pointer controllers + + if (requireListener && mWindowInfoListener == nullptr) { + mWindowInfoListener = sp::make(this); + auto initialInfo = std::make_pair(std::vector{}, + std::vector{}); +#if defined(__ANDROID__) + SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener, + &initialInfo); +#endif + onWindowInfosChangedLocked(initialInfo.first); + } else if (!requireListener && mWindowInfoListener != nullptr) { +#if defined(__ANDROID__) + SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener); +#endif + mWindowInfoListener = nullptr; + } } void PointerChoreographer::notifyPointerCaptureChanged( @@ -358,6 +406,12 @@ void PointerChoreographer::notifyPointerCaptureChanged( mNextListener.notify(args); } +void PointerChoreographer::onWindowInfosChanged( + const std::vector& windowInfos) { + std::scoped_lock _l(mLock); + onWindowInfosChangedLocked(windowInfos); +} + void PointerChoreographer::dump(std::string& dump) { std::scoped_lock _l(mLock); @@ -410,6 +464,7 @@ std::pair PointerChoreographer::ensureMous if (it == mMousePointersByDisplay.end()) { it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId)) .first; + // TODO (b/325252005): Add handing for mouse pointer controller } return {displayId, *it->second}; @@ -450,6 +505,8 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo auto [mousePointerIt, isNewMousePointer] = mMousePointersByDisplay.try_emplace(displayId, getMouseControllerConstructor(displayId)); + // TODO (b/325252005): Add handing for mouse pointer controller + mMouseDevices.emplace(info.getId()); if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) { mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE); @@ -488,6 +545,8 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo mInputDeviceInfos.end(); }); + onControllerAddedOrRemoved(); + // Check if we need to notify the policy if there's a change on the pointer display ID. return calculatePointerDisplayChangeToNotify(); } @@ -652,6 +711,31 @@ bool PointerChoreographer::setPointerIcon( return false; } +void PointerChoreographer::onWindowInfosChangedLocked( + const std::vector& windowInfos) { + // Mark all spot controllers secure on displays containing secure windows and + // remove secure flag from others if required + std::unordered_set privacySensitiveDisplays; + std::unordered_set allDisplayIds; + for (const auto& windowInfo : windowInfos) { + allDisplayIds.insert(windowInfo.displayId); + if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) && + windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) { + privacySensitiveDisplays.insert(windowInfo.displayId); + } + } + + for (auto& it : mTouchPointersByDevice) { + auto& pc = it.second; + for (int32_t displayId : allDisplayIds) { + pc->setSkipScreenshot(displayId, + privacySensitiveDisplays.find(displayId) != + privacySensitiveDisplays.end()); + } + } + // TODO (b/325252005): update skip screenshot flag for other types of pointer controllers +} + void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) { std::scoped_lock lock(mLock); if (visible) { @@ -702,4 +786,18 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusContr return ConstructorDelegate(std::move(ctor)); } +void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged( + const gui::WindowInfosUpdate& windowInfosUpdate) { + std::scoped_lock _l(mListenerLock); + if (mPointerChoreographer != nullptr) { + mPointerChoreographer->onWindowInfosChanged(windowInfosUpdate.windowInfos); + } +} + +void PointerChoreographer::PointerChoreographerDisplayInfoListener:: + onPointerChoreographerDestroyed() { + std::scoped_lock _l(mListenerLock); + mPointerChoreographer = nullptr; +} + } // namespace android diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index a3c210e696..b29d9cdb7c 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -21,6 +21,7 @@ #include "PointerChoreographerPolicyInterface.h" #include +#include #include namespace android { @@ -83,7 +84,7 @@ class PointerChoreographer : public PointerChoreographerInterface { public: explicit PointerChoreographer(InputListenerInterface& listener, PointerChoreographerPolicyInterface&); - ~PointerChoreographer() override = default; + ~PointerChoreographer() override; void setDefaultMouseDisplayId(int32_t displayId) override; void setDisplayViewports(const std::vector& viewports) override; @@ -106,6 +107,9 @@ public: void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; + // Public because it's also used by tests to simulate the WindowInfosListener callback + void onWindowInfosChanged(const std::vector& windowInfos); + void dump(std::string& dump) override; private: @@ -127,6 +131,22 @@ private: void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processDeviceReset(const NotifyDeviceResetArgs& args); + void onControllerAddedOrRemoved() REQUIRES(mLock); + void onWindowInfosChangedLocked(const std::vector& windowInfos) + REQUIRES(mLock); + + class PointerChoreographerDisplayInfoListener : public gui::WindowInfosListener { + public: + explicit PointerChoreographerDisplayInfoListener(PointerChoreographer* pc) + : mPointerChoreographer(pc){}; + void onWindowInfosChanged(const gui::WindowInfosUpdate&) override; + void onPointerChoreographerDestroyed(); + + private: + std::mutex mListenerLock; + PointerChoreographer* mPointerChoreographer GUARDED_BY(mListenerLock); + }; + sp mWindowInfoListener GUARDED_BY(mLock); using ControllerConstructor = ConstructorDelegate()>>; diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index c44486fc01..6b26e81637 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -143,6 +143,11 @@ public: /* Sets the custom pointer icon for mice or styluses. */ virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0; + + /* Sets the flag to skip screenshot of the pointer indicators on the display matching the + * provided displayId. + */ + virtual void setSkipScreenshot(int32_t displayId, bool skip) = 0; }; } // namespace android diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index dc199e2729..28d4b6751d 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -76,6 +76,14 @@ void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCustomIconStyle = icon.style; } +void FakePointerController::setSkipScreenshot(int32_t displayId, bool skip) { + if (skip) { + mDisplaysToSkipScreenshot.insert(displayId); + } else { + mDisplaysToSkipScreenshot.erase(displayId); + } +}; + void FakePointerController::assertViewportSet(int32_t displayId) { ASSERT_TRUE(mDisplayId); ASSERT_EQ(displayId, mDisplayId); @@ -117,6 +125,14 @@ void FakePointerController::assertCustomPointerIconNotSet() { ASSERT_EQ(std::nullopt, mCustomIconStyle); } +void FakePointerController::assertIsHiddenOnMirroredDisplays(int32_t displayId, bool isHidden) { + if (isHidden) { + ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end()); + } else { + ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end()); + } +} + bool FakePointerController::isPointerShown() { return mIsPointerShown; } diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index 536b447215..b5b982e012 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace android { @@ -45,6 +46,7 @@ public: void setDisplayViewport(const DisplayViewport& viewport) override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; + void setSkipScreenshot(int32_t displayId, bool skip) override; void fade(Transition) override; void assertViewportSet(int32_t displayId); @@ -55,6 +57,7 @@ public: void assertPointerIconNotSet(); void assertCustomPointerIconSet(PointerIconStyle iconId); void assertCustomPointerIconNotSet(); + void assertIsHiddenOnMirroredDisplays(int32_t displayId, bool isHidden); bool isPointerShown(); private: @@ -77,6 +80,7 @@ private: std::optional mCustomIconStyle; std::map> mSpotsByDisplay; + std::unordered_set mDisplaysToSkipScreenshot; }; } // namespace android diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 11c6b7e423..d81f8a0280 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -15,7 +15,8 @@ */ #include "../PointerChoreographer.h" - +#include +#include #include #include #include @@ -27,6 +28,8 @@ namespace android { +namespace input_flags = com::android::input::flags; + using ControllerType = PointerControllerInterface::ControllerType; using testing::AllOf; @@ -1587,6 +1590,76 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) { firstMousePc->assertPointerIconNotSet(); } +TEST_F_WITH_FLAGS(PointerChoreographerTest, HidesTouchSpotsOnMirroredDisplaysForSecureWindow, + REQUIRES_FLAGS_ENABLED( + ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) { + // Add a touch device and enable show touches. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + + // Emit touch events to create PointerController + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // By default touch indicators should not be hidden + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); + pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); + + // adding secure window on display should set flag to hide pointer indicators on corresponding + // mirrored display + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + mChoreographer.onWindowInfosChanged({windowInfo}); + pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/true); + pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); + + // removing the secure window should reset the state + windowInfo.inputConfig.clear(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY); + mChoreographer.onWindowInfosChanged({windowInfo}); + pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); + pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); +} + +TEST_F_WITH_FLAGS(PointerChoreographerTest, + DoesNotHidesTouchSpotsOnMirroredDisplaysForInvisibleWindow, + REQUIRES_FLAGS_ENABLED( + ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) { + // Add a touch device and enable show touches. + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + + // Emit touch events to create PointerController + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // By default touch indicators should not be hidden + auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); + pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); + + // adding secure but hidden window on display should still not set flag to hide pointer + // indicators + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE; + mChoreographer.onWindowInfosChanged({windowInfo}); + pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); + pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); +} + TEST_P(StylusTestFixture, SetsPointerIconForStylus) { const auto& [name, source, controllerType] = GetParam(); diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 7898126944..2602ebbd14 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -289,6 +289,7 @@ public: void setDisplayViewport(const DisplayViewport& displayViewport) override {} void updatePointerIcon(PointerIconStyle iconId) override {} void setCustomPointerIcon(const SpriteIcon& icon) override {} + void setSkipScreenshot(int32_t displayId, bool skip) override{}; std::string dump() override { return ""; } }; -- GitLab From dce110aa33f4e8fa16b4603bcfbab596e260f319 Mon Sep 17 00:00:00 2001 From: Robert Horvath Date: Fri, 16 Sep 2022 11:08:47 +0200 Subject: [PATCH 313/465] Prevent SurfaceFlinger from turning on display on quiescent boot During a quiescent boot the screen is supposed to stay off, until the user explicitly wakes the device again. Therefore SurfaceFlinger should not unconditionally power on the display when initializing. Test: manual: `adb reboot quiescent` and observe calls to setPowerMode Bug: 230576393 Bug: 230818009 Change-Id: Iaf5a9aabb484cdc4645dcb0e819b2cbf49d179e8 --- services/surfaceflinger/SurfaceFlinger.cpp | 15 +++++++++++---- services/surfaceflinger/SurfaceFlinger.h | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9d29c416cd..2d52d02ae0 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -424,7 +424,8 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag) mInternalDisplayDensity( getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)), mPowerAdvisor(std::make_unique(*this)), - mWindowInfosListenerInvoker(sp::make()) { + mWindowInfosListenerInvoker(sp::make()), + mSkipPowerOnForQuiescent(base::GetBoolProperty("ro.boot.quiescent"s, false)) { ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str()); } @@ -3882,7 +3883,9 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, if (currentState.physical) { const auto display = getDisplayDeviceLocked(displayToken); - setPowerModeInternal(display, hal::PowerMode::ON); + if (!mSkipPowerOnForQuiescent) { + setPowerModeInternal(display, hal::PowerMode::ON); + } // TODO(b/175678251) Call a listener instead. if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) { @@ -6101,8 +6104,11 @@ void SurfaceFlinger::initializeDisplays() { // Power on all displays. The primary display is first, so becomes the active display. Also, // the DisplayCapability set of a display is populated on its first powering on. Do this now // before responding to any Binder query from DisplayManager about display capabilities. - for (const auto& [id, display] : mPhysicalDisplays) { - setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON); + // Additionally, do not turn on displays if the boot should be quiescent. + if (!mSkipPowerOnForQuiescent) { + for (const auto& [id, display] : mPhysicalDisplays) { + setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON); + } } } } @@ -6261,6 +6267,7 @@ void SurfaceFlinger::setPowerModeInternal(const sp& display, hal: void SurfaceFlinger::setPowerMode(const sp& displayToken, int mode) { auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD( kMainThreadContext) { + mSkipPowerOnForQuiescent = false; const auto display = getDisplayDeviceLocked(displayToken); if (!display) { ALOGE("Attempt to set power mode %d for invalid display token %p", mode, diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fc50fd97f8..24444241c9 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1491,6 +1491,8 @@ private: bool mPowerHintSessionEnabled; bool mLayerLifecycleManagerEnabled = false; + // Whether a display should be turned on when initialized + bool mSkipPowerOnForQuiescent; frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext); frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext); -- GitLab From 16490fdbfbf667784ffa22247e4e6fb36eaa1052 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 7 May 2024 16:08:36 -0700 Subject: [PATCH 314/465] SF: do not wait for LastCompositeEventThread if it is not used Bug: 333248925 Bug: 162235855 Test: SysUI Jank Regression: perfetto_cuj_systemui-SPLASHSCREEN_EXIT_ANIM-timeline_metrics-missed_sf_frames-mean Change-Id: I0aa85f676c9fe789cbe31d88a529a9ec7aa72341 --- services/surfaceflinger/Scheduler/Scheduler.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index ccb3aa712a..ccaa05f970 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -346,7 +346,9 @@ private: // Used to skip event dispatch before EventThread creation during boot. // TODO: b/241285191 - Reorder Scheduler initialization to avoid this. bool hasEventThreads() const { - return CC_LIKELY(mRenderEventThread && mLastCompositeEventThread); + return CC_LIKELY( + mRenderEventThread && + (FlagManager::getInstance().deprecate_vsync_sf() || mLastCompositeEventThread)); } EventThread& eventThreadFor(Cycle cycle) const { -- GitLab From 7235222ce7993305b861674a7b2b7f73db40676b Mon Sep 17 00:00:00 2001 From: Linnan Li Date: Fri, 12 Apr 2024 18:55:57 +0800 Subject: [PATCH 315/465] Associate device id with getWallpaperWindow Because we have supported multi-device event streams in the InputDispatcher, but there are still some logics that use the previous single-device stream logic. Here, associating getWallpaperWindow with deviceId can solve wallpaper window is wrong issue in event transfer. In addition to that, this patch also attempts to fix an issue where the foreground target window with a wallpaper could not receive subsequent remaining events after a transferTouchGesture. This is because, in transferWallpaperTouch, we merged part of the state from the InputState of the target foreground window into its corresponding wallpaper window, causing the firstNewPointerIdx in its InputState to be reset. Here, we try to resolve this issue by moving the synthesis operation of the down event for the target foreground window to after the transferWallpaperTouch. Bug: 328718622 Test: atest inputflinger_tests Change-Id: If1085c8aefd962cf1e05b7bf68d87c9f2c8bacc9 Signed-off-by: Linnan Li --- .../dispatcher/InputDispatcher.cpp | 62 ++-- .../inputflinger/dispatcher/InputDispatcher.h | 4 +- .../inputflinger/dispatcher/TouchState.cpp | 8 +- services/inputflinger/dispatcher/TouchState.h | 2 +- .../tests/InputDispatcher_test.cpp | 337 ++++++++++++++++-- 5 files changed, 358 insertions(+), 55 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index de841ba2b8..79b8560590 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5363,31 +5363,32 @@ void InputDispatcher::setInputWindowsLocked( onFocusChangedLocked(*changes, traceContext.getTracker(), removedFocusedWindowHandle); } - std::unordered_map::iterator stateIt = - mTouchStatesByDisplay.find(displayId); - if (stateIt != mTouchStatesByDisplay.end()) { - TouchState& state = stateIt->second; + if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) { + TouchState& state = it->second; for (size_t i = 0; i < state.windows.size();) { TouchedWindow& touchedWindow = state.windows[i]; - if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) { - LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName() - << " in display %" << displayId; - CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, - "touched window was removed", traceContext.getTracker()); - synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options); - // Since we are about to drop the touch, cancel the events for the wallpaper as - // well. - if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) && - touchedWindow.windowHandle->getInfo()->inputConfig.test( - gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { - if (const auto& ww = state.getWallpaperWindow(); ww) { + if (getWindowHandleLocked(touchedWindow.windowHandle) != nullptr) { + i++; + continue; + } + LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName() + << " in display %" << displayId; + CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, + "touched window was removed", traceContext.getTracker()); + synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options); + // Since we are about to drop the touch, cancel the events for the wallpaper as + // well. + if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) && + touchedWindow.windowHandle->getInfo()->inputConfig.test( + gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) { + for (const DeviceId deviceId : touchedWindow.getTouchingDeviceIds()) { + if (const auto& ww = state.getWallpaperWindow(deviceId); ww != nullptr) { + options.deviceId = deviceId; synthesizeCancelationEventsForWindowLocked(ww, options); } } - state.windows.erase(state.windows.begin() + i); - } else { - ++i; } + state.windows.erase(state.windows.begin() + i); } // If drag window is gone, it would receive a cancel event and broadcast the DRAG_END. We @@ -5662,13 +5663,13 @@ bool InputDispatcher::transferTouchGesture(const sp& fromToken, const s ALOGD("Touch transfer failed because from window is not being touched."); return false; } - std::set deviceIds = touchedWindow->getTouchingDeviceIds(); + std::set deviceIds = touchedWindow->getTouchingDeviceIds(); if (deviceIds.size() != 1) { LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds) << " for window: " << touchedWindow->dump(); return false; } - const int32_t deviceId = *deviceIds.begin(); + const DeviceId deviceId = *deviceIds.begin(); const sp fromWindowHandle = touchedWindow->windowHandle; const sp toWindowHandle = getWindowHandleLocked(toToken, displayId); @@ -5722,13 +5723,18 @@ bool InputDispatcher::transferTouchGesture(const sp& fromToken, const s "transferring touch from this window to another window", traceContext.getTracker()); synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection); - synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection, - newTargetFlags, - traceContext.getTracker()); // Check if the wallpaper window should deliver the corresponding event. transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle, *state, deviceId, pointers, traceContext.getTracker()); + + // Because new window may have a wallpaper window, it will merge input state from it + // parent window, after this the firstNewPointerIdx in input state will be reset, then + // it will cause new move event be thought inconsistent, so we should synthesize the + // down event after it reset. + synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection, + newTargetFlags, + traceContext.getTracker()); } } // release lock @@ -7039,7 +7045,7 @@ void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanosecon void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFlags, const sp& oldWindowHandle, const sp& newWindowHandle, - TouchState& state, int32_t deviceId, + TouchState& state, DeviceId deviceId, const PointerProperties& pointerProperties, std::vector& targets) const { std::vector pointers{pointerProperties}; @@ -7049,7 +7055,7 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFl newWindowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); const sp oldWallpaper = - oldHasWallpaper ? state.getWallpaperWindow() : nullptr; + oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr; const sp newWallpaper = newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr; if (oldWallpaper == newWallpaper) { @@ -7075,7 +7081,7 @@ void InputDispatcher::slipWallpaperTouch(ftl::Flags targetFl void InputDispatcher::transferWallpaperTouch( ftl::Flags oldTargetFlags, ftl::Flags newTargetFlags, const sp fromWindowHandle, - const sp toWindowHandle, TouchState& state, int32_t deviceId, + const sp toWindowHandle, TouchState& state, DeviceId deviceId, const std::vector& pointers, const std::unique_ptr& traceTracker) { const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) && @@ -7086,7 +7092,7 @@ void InputDispatcher::transferWallpaperTouch( gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER); const sp oldWallpaper = - oldHasWallpaper ? state.getWallpaperWindow() : nullptr; + oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr; const sp newWallpaper = newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr; if (oldWallpaper == newWallpaper) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 3579a67c96..3d127c296a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -701,14 +701,14 @@ private: void slipWallpaperTouch(ftl::Flags targetFlags, const sp& oldWindowHandle, const sp& newWindowHandle, - TouchState& state, int32_t deviceId, + TouchState& state, DeviceId deviceId, const PointerProperties& pointerProperties, std::vector& targets) const REQUIRES(mLock); void transferWallpaperTouch(ftl::Flags oldTargetFlags, ftl::Flags newTargetFlags, const sp fromWindowHandle, const sp toWindowHandle, - TouchState& state, int32_t deviceId, + TouchState& state, DeviceId deviceId, const std::vector& pointers, const std::unique_ptr& traceTracker) REQUIRES(mLock); diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp index 296c334583..0c9ad3c7b7 100644 --- a/services/inputflinger/dispatcher/TouchState.cpp +++ b/services/inputflinger/dispatcher/TouchState.cpp @@ -211,9 +211,11 @@ bool TouchState::isSlippery(DeviceId deviceId) const { return haveSlipperyForegroundWindow; } -sp TouchState::getWallpaperWindow() const { - for (size_t i = 0; i < windows.size(); i++) { - const TouchedWindow& window = windows[i]; +sp TouchState::getWallpaperWindow(DeviceId deviceId) const { + for (const auto& window : windows) { + if (!window.hasTouchingPointers(deviceId)) { + continue; + } if (window.windowHandle->getInfo()->inputConfig.test( gui::WindowInfo::InputConfig::IS_WALLPAPER)) { return window.windowHandle; diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h index 9ddb4e206f..9d4bb3d943 100644 --- a/services/inputflinger/dispatcher/TouchState.h +++ b/services/inputflinger/dispatcher/TouchState.h @@ -66,7 +66,7 @@ struct TouchState { sp getFirstForegroundWindowHandle(DeviceId deviceId) const; bool isSlippery(DeviceId deviceId) const; - sp getWallpaperWindow() const; + sp getWallpaperWindow(DeviceId deviceId) const; const TouchedWindow& getTouchedWindow( const sp& windowHandle) const; // Whether any of the windows are currently being touched diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index bc173b16ce..09b358afe5 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -117,7 +117,7 @@ static constexpr gui::Uid SECONDARY_WINDOW_UID{1012}; // An arbitrary pid of the gesture monitor window static constexpr gui::Pid MONITOR_PID{2001}; -static constexpr int expectedWallpaperFlags = +static constexpr int EXPECTED_WALLPAPER_FLAGS = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID; @@ -827,7 +827,7 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance // Both foreground window and its wallpaper should receive the touch down foregroundWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, @@ -837,13 +837,13 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); - wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Now the foreground window goes away, but the wallpaper stays mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0}); foregroundWindow->consumeMotionCancel(); // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too. - wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); } /** @@ -908,7 +908,7 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { // Both foreground window and its wallpaper should receive the touch down foregroundWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, @@ -916,7 +916,7 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionMove(); - wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Wallpaper closes its channel, but the window remains. wallpaperWindow->destroyReceiver(); @@ -928,6 +928,301 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { foregroundWindow->consumeMotionCancel(); } +/** + * Two windows: left and right, and a separate wallpaper window underneath each. Device A sends a + * down event to the left window. Device B sends a down event to the right window. Next, the right + * window disappears. Both the right window and its wallpaper window should receive cancel event. + * The left window and its wallpaper window should not receive any events. + */ +TEST_F(InputDispatcherTest, MultiDeviceDisappearingWindowWithWallpaperWindows) { + std::shared_ptr application = std::make_shared(); + sp leftForegroundWindow = + sp::make(application, mDispatcher, "Left foreground window", + ADISPLAY_ID_DEFAULT); + leftForegroundWindow->setFrame(Rect(0, 0, 100, 100)); + leftForegroundWindow->setDupTouchToWallpaper(true); + sp leftWallpaperWindow = + sp::make(application, mDispatcher, "Left wallpaper window", + ADISPLAY_ID_DEFAULT); + leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100)); + leftWallpaperWindow->setIsWallpaper(true); + + sp rightForegroundWindow = + sp::make(application, mDispatcher, "Right foreground window", + ADISPLAY_ID_DEFAULT); + rightForegroundWindow->setFrame(Rect(100, 0, 200, 100)); + rightForegroundWindow->setDupTouchToWallpaper(true); + sp rightWallpaperWindow = + sp::make(application, mDispatcher, "Right wallpaper window", + ADISPLAY_ID_DEFAULT); + rightWallpaperWindow->setFrame(Rect(100, 0, 200, 100)); + rightWallpaperWindow->setIsWallpaper(true); + + mDispatcher->onWindowInfosChanged( + {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(), + *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()}, + {}, + 0, + 0}); + + const DeviceId deviceA = 9; + const DeviceId deviceB = 3; + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .deviceId(deviceA) + .build()); + leftForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA))); + leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceA), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .deviceId(deviceB) + .build()); + rightForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB))); + rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + + // Now right foreground window disappears, but right wallpaper window remains. + mDispatcher->onWindowInfosChanged( + {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(), + *rightWallpaperWindow->getInfo()}, + {}, + 0, + 0}); + + // Left foreground window and left wallpaper window still exist, and should not receive any + // events. + leftForegroundWindow->assertNoEvents(); + leftWallpaperWindow->assertNoEvents(); + // Since right foreground window disappeared, right wallpaper window and right foreground window + // should receive cancel events. + rightForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB))); + rightWallpaperWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED))); +} + +/** + * Three windows arranged horizontally and without any overlap. Every window has a + * wallpaper window underneath. The middle window also has SLIPPERY flag. + * Device A sends a down event to the left window. Device B sends a down event to the middle window. + * Next, device B sends move event to the right window. Touch for device B should slip from the + * middle window to the right window. Also, the right wallpaper window should receive a down event. + * The middle window and its wallpaper window should receive a cancel event. The left window should + * not receive any events. If device B continues to report events, the right window and its + * wallpaper window should receive remaining events. + */ +TEST_F(InputDispatcherTest, MultiDeviceSlipperyTouchWithWallpaperWindow) { + std::shared_ptr application = std::make_shared(); + sp leftForegroundWindow = + sp::make(application, mDispatcher, "Left foreground window", + ADISPLAY_ID_DEFAULT); + leftForegroundWindow->setFrame(Rect(0, 0, 100, 100)); + leftForegroundWindow->setDupTouchToWallpaper(true); + sp leftWallpaperWindow = + sp::make(application, mDispatcher, "Left wallpaper window", + ADISPLAY_ID_DEFAULT); + leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100)); + leftWallpaperWindow->setIsWallpaper(true); + + sp middleForegroundWindow = + sp::make(application, mDispatcher, "Middle foreground window", + ADISPLAY_ID_DEFAULT); + middleForegroundWindow->setFrame(Rect(100, 0, 200, 100)); + middleForegroundWindow->setDupTouchToWallpaper(true); + middleForegroundWindow->setSlippery(true); + sp middleWallpaperWindow = + sp::make(application, mDispatcher, "Middle wallpaper window", + ADISPLAY_ID_DEFAULT); + middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100)); + middleWallpaperWindow->setIsWallpaper(true); + + sp rightForegroundWindow = + sp::make(application, mDispatcher, "Right foreground window", + ADISPLAY_ID_DEFAULT); + rightForegroundWindow->setFrame(Rect(200, 0, 300, 100)); + rightForegroundWindow->setDupTouchToWallpaper(true); + sp rightWallpaperWindow = + sp::make(application, mDispatcher, "Right wallpaper window", + ADISPLAY_ID_DEFAULT); + rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100)); + rightWallpaperWindow->setIsWallpaper(true); + + mDispatcher->onWindowInfosChanged( + {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(), + *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(), + *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()}, + {}, + 0, + 0}); + + const DeviceId deviceA = 9; + const DeviceId deviceB = 3; + // Device A sends a DOWN event to the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .deviceId(deviceA) + .build()); + leftForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA))); + leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceA), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + // Device B sends a DOWN event to the middle window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .deviceId(deviceB) + .build()); + middleForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB))); + middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + // Move the events of device B to the top of the right window. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50)) + .deviceId(deviceB) + .build()); + middleForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB))); + middleWallpaperWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED))); + rightForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB))); + rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + // Make sure the window on the right can receive the remaining events. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51)) + .deviceId(deviceB) + .build()); + leftForegroundWindow->assertNoEvents(); + leftWallpaperWindow->assertNoEvents(); + middleForegroundWindow->assertNoEvents(); + middleWallpaperWindow->assertNoEvents(); + rightForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB))); + rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), + WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); +} + +/** + * Similar to the test above, we have three windows, they are arranged horizontally and without any + * overlap, and every window has a wallpaper window. The middle window is a simple window, without + * any special flags. Device A reports a down event that lands in left window. Device B sends a down + * event to the middle window and then touch is transferred from the middle window to the right + * window. The right window and its wallpaper window should receive a down event. The middle window + * and its wallpaper window should receive a cancel event. The left window should not receive any + * events. Subsequent events reported by device B should go to the right window and its wallpaper. + */ +TEST_F(InputDispatcherTest, MultiDeviceTouchTransferWithWallpaperWindows) { + std::shared_ptr application = std::make_shared(); + sp leftForegroundWindow = + sp::make(application, mDispatcher, "Left foreground window", + ADISPLAY_ID_DEFAULT); + leftForegroundWindow->setFrame(Rect(0, 0, 100, 100)); + leftForegroundWindow->setDupTouchToWallpaper(true); + sp leftWallpaperWindow = + sp::make(application, mDispatcher, "Left wallpaper window", + ADISPLAY_ID_DEFAULT); + leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100)); + leftWallpaperWindow->setIsWallpaper(true); + + sp middleForegroundWindow = + sp::make(application, mDispatcher, "Middle foreground window", + ADISPLAY_ID_DEFAULT); + middleForegroundWindow->setFrame(Rect(100, 0, 200, 100)); + middleForegroundWindow->setDupTouchToWallpaper(true); + sp middleWallpaperWindow = + sp::make(application, mDispatcher, "Middle wallpaper window", + ADISPLAY_ID_DEFAULT); + middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100)); + middleWallpaperWindow->setIsWallpaper(true); + + sp rightForegroundWindow = + sp::make(application, mDispatcher, "Right foreground window", + ADISPLAY_ID_DEFAULT); + rightForegroundWindow->setFrame(Rect(200, 0, 300, 100)); + rightForegroundWindow->setDupTouchToWallpaper(true); + sp rightWallpaperWindow = + sp::make(application, mDispatcher, "Right wallpaper window", + ADISPLAY_ID_DEFAULT); + rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100)); + rightWallpaperWindow->setIsWallpaper(true); + + mDispatcher->onWindowInfosChanged( + {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(), + *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(), + *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()}, + {}, + 0, + 0}); + + const DeviceId deviceA = 9; + const DeviceId deviceB = 3; + // Device A touch down on the left window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .deviceId(deviceA) + .build()); + leftForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA))); + leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceA), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + // Device B touch down on the middle window + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .deviceId(deviceB) + .build()); + middleForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB))); + middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); + + // Transfer touch from the middle window to the right window. + ASSERT_TRUE(mDispatcher->transferTouchGesture(middleForegroundWindow->getToken(), + rightForegroundWindow->getToken())); + + middleForegroundWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB))); + middleWallpaperWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED))); + rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), + WithDeviceId(deviceB), + WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))); + rightWallpaperWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))); + + // Make sure the right window can receive the remaining events. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51)) + .deviceId(deviceB) + .build()); + leftForegroundWindow->assertNoEvents(); + leftWallpaperWindow->assertNoEvents(); + middleForegroundWindow->assertNoEvents(); + middleWallpaperWindow->assertNoEvents(); + rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), + WithDeviceId(deviceB), + WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))); + rightWallpaperWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB), + WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))); +} + class ShouldSplitTouchFixture : public InputDispatcherTest, public ::testing::WithParamInterface {}; INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture, @@ -959,7 +1254,7 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { // Both top window and its wallpaper should receive the touch down foregroundWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Second finger down on the top window const MotionEvent secondFingerDownEvent = @@ -975,7 +1270,7 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1); wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, - expectedWallpaperFlags); + EXPECTED_WALLPAPER_FLAGS); const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) @@ -989,7 +1284,7 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionPointerUp(0); - wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, @@ -1004,7 +1299,7 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); } /** @@ -1046,7 +1341,7 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { // Both foreground window and its wallpaper should receive the touch down leftWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Second finger down on the right window const MotionEvent secondFingerDownEvent = @@ -1064,14 +1359,14 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { // Since the touch is split, right window gets ACTION_DOWN rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, - expectedWallpaperFlags); + EXPECTED_WALLPAPER_FLAGS); // Now, leftWindow, which received the first finger, disappears. mDispatcher->onWindowInfosChanged( {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0}); leftWindow->consumeMotionCancel(); // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too. - wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // The pointer that's still down on the right window moves, and goes to the right window only. // As far as the dispatcher's concerned though, both pointers are still present. @@ -1126,7 +1421,7 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { // Both foreground window and its wallpaper should receive the touch down leftWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Move to right window, the left window should receive cancel. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -1136,7 +1431,7 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { leftWindow->consumeMotionCancel(); rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); } /** @@ -5763,7 +6058,7 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); - wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Dispatcher reports pointer down outside focus for the wallpaper mFakePolicy->assertOnPointerDownEquals(wallpaper->getToken()); @@ -5774,7 +6069,7 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); - wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); // There should not be any changes to the focused window when transferring touch ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertOnPointerDownWasNotCalled()); @@ -5935,7 +6230,7 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); - wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); wallpaper2->assertNoEvents(); // Transfer touch focus to the second window @@ -5946,9 +6241,9 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); - wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags); + wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, - expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, @@ -5958,7 +6253,7 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper1->assertNoEvents(); wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, - expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } // For the cases of single pointer touch and two pointers non-split touch, the api's -- GitLab From 77527b9787e25d3f07896aba50d74008b43b1bcf Mon Sep 17 00:00:00 2001 From: Yi Kong Date: Wed, 8 May 2024 13:27:09 +0800 Subject: [PATCH 316/465] Turn off integer overflow UBSAN checks for hash functions These functions intentionally overflows integers. The upcoming compiler update, clang-r522817, will start complaining about these overflows. Test: boot with new compiler Bug: 325934863 Change-Id: Idb3730c652ae739b73c09517665b518a79b60345 --- include/ftl/details/hash.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/ftl/details/hash.h b/include/ftl/details/hash.h index f9a7fa8d41..230ae51257 100644 --- a/include/ftl/details/hash.h +++ b/include/ftl/details/hash.h @@ -43,6 +43,7 @@ constexpr std::uint64_t shift_mix(std::uint64_t v) { return v ^ (v >> 47); } +__attribute__((no_sanitize("unsigned-integer-overflow"))) constexpr std::uint64_t hash_length_16(std::uint64_t u, std::uint64_t v) { constexpr std::uint64_t kPrime = 0x9ddfea08eb382d69ull; auto a = (u ^ v) * kPrime; @@ -58,6 +59,7 @@ constexpr std::uint64_t kPrime1 = 0xb492b66fbe98f273ull; constexpr std::uint64_t kPrime2 = 0x9ae16a3b2f90404full; constexpr std::uint64_t kPrime3 = 0xc949d7c7509e6557ull; +__attribute__((no_sanitize("unsigned-integer-overflow"))) inline std::uint64_t hash_length_0_to_16(const char* str, std::uint64_t length) { if (length > 8) { const auto a = read_unaligned(str); @@ -80,6 +82,7 @@ inline std::uint64_t hash_length_0_to_16(const char* str, std::uint64_t length) return kPrime2; } +__attribute__((no_sanitize("unsigned-integer-overflow"))) inline std::uint64_t hash_length_17_to_32(const char* str, std::uint64_t length) { const auto a = read_unaligned(str) * kPrime1; const auto b = read_unaligned(str + 8); @@ -89,6 +92,7 @@ inline std::uint64_t hash_length_17_to_32(const char* str, std::uint64_t length) a + rotate(b ^ kPrime3, 20) - c + length); } +__attribute__((no_sanitize("unsigned-integer-overflow"))) inline std::uint64_t hash_length_33_to_64(const char* str, std::uint64_t length) { auto z = read_unaligned(str + 24); auto a = read_unaligned(str) + (length + read_unaligned(str + length - 16)) * kPrime0; -- GitLab From 2120b9e475fabec7ee44783111379b5c49d248cf Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Sat, 4 May 2024 00:06:37 +0000 Subject: [PATCH 317/465] InputReader: Cleanup after PointerChoreographer refactor Bug: 311416205 Test: atest inputflinger_tests Change-Id: If08a7e717504457b8e061796df15700f9308144f --- .../inputflinger/include/InputReaderBase.h | 11 ---- services/inputflinger/reader/InputReader.cpp | 56 ------------------- .../inputflinger/reader/include/InputReader.h | 11 ---- .../reader/include/InputReaderContext.h | 4 -- .../reader/mapper/KeyboardInputMapper.cpp | 7 +-- .../tests/FakeInputReaderPolicy.cpp | 14 ----- .../tests/FakeInputReaderPolicy.h | 6 -- .../inputflinger/tests/InputReader_test.cpp | 7 --- services/inputflinger/tests/InterfaceMocks.h | 5 -- .../tests/KeyboardInputMapper_test.cpp | 3 - .../tests/fuzzers/MapperHelpers.h | 47 +--------------- 11 files changed, 2 insertions(+), 169 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 77e672c6bc..4d8ed99eb4 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -61,9 +61,6 @@ struct InputReaderConfiguration { // The display size or orientation changed. DISPLAY_INFO = 1u << 2, - // The visible touches option changed. - SHOW_TOUCHES = 1u << 3, - // The keyboard layouts must be reloaded. KEYBOARD_LAYOUTS = 1u << 4, @@ -214,9 +211,6 @@ struct InputReaderConfiguration { // will cover this portion of the display diagonal. float pointerGestureZoomSpeedRatio; - // True to show the location of touches on the touch screen as spots. - bool showTouches; - // The latest request to enable or disable Pointer Capture. PointerCaptureRequest pointerCaptureRequest; @@ -268,7 +262,6 @@ struct InputReaderConfiguration { pointerGestureSwipeMaxWidthRatio(0.25f), pointerGestureMovementSpeedRatio(0.8f), pointerGestureZoomSpeedRatio(0.3f), - showTouches(false), pointerCaptureRequest(), touchpadPointerSpeed(0), touchpadNaturalScrollingEnabled(true), @@ -449,10 +442,6 @@ public: /* Gets the input reader configuration. */ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0; - /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */ - virtual std::shared_ptr obtainPointerController( - int32_t deviceId) = 0; - /* Notifies the input reader policy that some input devices have changed * and provides information about all current input devices. */ diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 9608210ca0..903c6ed9bc 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -402,10 +402,6 @@ void InputReader::refreshConfigurationLocked(ConfigurationChanges changes) { ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str()); nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - if (changes.test(Change::DISPLAY_INFO)) { - updatePointerDisplayLocked(); - } - if (changes.test(Change::MUST_REOPEN)) { mEventHub->requestReopenDevices(); } else { @@ -490,47 +486,6 @@ bool InputReader::shouldDropVirtualKeyLocked(nsecs_t now, int32_t keyCode, int32 } } -std::shared_ptr InputReader::getPointerControllerLocked( - int32_t deviceId) { - std::shared_ptr controller = mPointerController.lock(); - if (controller == nullptr) { - controller = mPolicy->obtainPointerController(deviceId); - mPointerController = controller; - updatePointerDisplayLocked(); - } - return controller; -} - -void InputReader::updatePointerDisplayLocked() { - std::shared_ptr controller = mPointerController.lock(); - if (controller == nullptr) { - return; - } - - std::optional viewport = - mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId); - if (!viewport) { - ALOGW("Can't find the designated viewport with ID %" PRId32 " to update cursor input " - "mapper. Fall back to default display", - mConfig.defaultPointerDisplayId); - viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT); - } - if (!viewport) { - ALOGE("Still can't find a viable viewport to update cursor input mapper. Skip setting it to" - " PointerController."); - return; - } - - controller->setDisplayViewport(*viewport); -} - -void InputReader::fadePointerLocked() { - std::shared_ptr controller = mPointerController.lock(); - if (controller != nullptr) { - controller->fade(PointerControllerInterface::Transition::GRADUAL); - } -} - void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) { if (when < mNextTimeout) { mNextTimeout = when; @@ -1067,17 +1022,6 @@ bool InputReader::ContextImpl::shouldDropVirtualKey(nsecs_t now, int32_t keyCode return mReader->shouldDropVirtualKeyLocked(now, keyCode, scanCode); } -void InputReader::ContextImpl::fadePointer() { - // lock is already held by the input loop - mReader->fadePointerLocked(); -} - -std::shared_ptr InputReader::ContextImpl::getPointerController( - int32_t deviceId) { - // lock is already held by the input loop - return mReader->getPointerControllerLocked(deviceId); -} - void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) { // lock is already held by the input loop mReader->requestTimeoutAtTimeLocked(when); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 4c78db38cc..5f882cfcd3 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -16,7 +16,6 @@ #pragma once -#include #include #include #include @@ -141,9 +140,6 @@ protected: void disableVirtualKeysUntil(nsecs_t time) REQUIRES(mReader->mLock) override; bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) REQUIRES(mReader->mLock) override; - void fadePointer() REQUIRES(mReader->mLock) override; - std::shared_ptr getPointerController(int32_t deviceId) - REQUIRES(mReader->mLock) override; void requestTimeoutAtTime(nsecs_t when) REQUIRES(mReader->mLock) override; int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override; void getExternalStylusDevices(std::vector& outDevices) @@ -230,13 +226,6 @@ private: [[nodiscard]] std::list dispatchExternalStylusStateLocked(const StylusState& state) REQUIRES(mLock); - // The PointerController that is shared among all the input devices that need it. - std::weak_ptr mPointerController; - std::shared_ptr getPointerControllerLocked(int32_t deviceId) - REQUIRES(mLock); - void updatePointerDisplayLocked() REQUIRES(mLock); - void fadePointerLocked() REQUIRES(mLock); - int32_t mGeneration GUARDED_BY(mLock); int32_t bumpGenerationLocked() REQUIRES(mLock); diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h index 69b2315a6c..907a49faa2 100644 --- a/services/inputflinger/reader/include/InputReaderContext.h +++ b/services/inputflinger/reader/include/InputReaderContext.h @@ -28,7 +28,6 @@ class InputDevice; class InputListenerInterface; class InputMapper; class InputReaderPolicyInterface; -class PointerControllerInterface; struct StylusState; /* Internal interface used by individual input devices to access global input device state @@ -45,9 +44,6 @@ public: virtual void disableVirtualKeysUntil(nsecs_t time) = 0; virtual bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) = 0; - virtual void fadePointer() = 0; - virtual std::shared_ptr getPointerController(int32_t deviceId) = 0; - virtual void requestTimeoutAtTime(nsecs_t when) = 0; virtual int32_t bumpGeneration() = 0; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 738517b67e..658ceabcc3 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -483,16 +483,11 @@ std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) { InputReaderContext& context = *getContext(); context.setLastKeyDownTimestamp(downTime); - if (context.isPreventingTouchpadTaps()) { - // avoid pinging java service unnecessarily, just fade pointer again if it became visible - context.fadePointer(); - return; - } + // TODO(b/338652288): Move cursor fading logic into PointerChoreographer. // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard // shortcuts bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode); if (shouldHideCursor && context.getPolicy()->isInputMethodConnectionActive()) { - context.fadePointer(); context.setPreventingTouchpadTaps(true); } } diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index e9118a98c9..e2dcb41ac0 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -155,11 +155,6 @@ void FakeInputReaderPolicy::removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); } -void FakeInputReaderPolicy::setPointerController( - std::shared_ptr controller) { - mPointerController = std::move(controller); -} - const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const { return mConfig; } @@ -183,10 +178,6 @@ PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp return mConfig.pointerCaptureRequest; } -void FakeInputReaderPolicy::setShowTouches(bool enabled) { - mConfig.showTouches = enabled; -} - void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) { mConfig.defaultPointerDisplayId = pointerDisplayId; } @@ -228,11 +219,6 @@ void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* out *outConfig = mConfig; } -std::shared_ptr FakeInputReaderPolicy::obtainPointerController( - int32_t /*deviceId*/) { - return mPointerController; -} - void FakeInputReaderPolicy::notifyInputDevicesChanged( const std::vector& inputDevices) { std::scoped_lock lock(mLock); diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 710bb5496f..88f0ba7f9b 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -26,7 +26,6 @@ #include #include -#include "FakePointerController.h" #include "input/DisplayViewport.h" #include "input/InputDevice.h" @@ -62,14 +61,12 @@ public: const KeyboardLayoutInfo& layoutInfo); void addDisabledDevice(int32_t deviceId); void removeDisabledDevice(int32_t deviceId); - void setPointerController(std::shared_ptr controller); const InputReaderConfiguration& getReaderConfiguration() const; const std::vector getInputDevices() const; TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor, ui::Rotation surfaceRotation); void setTouchAffineTransformation(const TouchAffineTransformation t); PointerCaptureRequest setPointerCapture(const sp& window); - void setShowTouches(bool enabled); void setDefaultPointerDisplayId(int32_t pointerDisplayId); void setPointerGestureEnabled(bool enabled); float getPointerGestureMovementSpeedRatio(); @@ -84,8 +81,6 @@ public: private: void getReaderConfiguration(InputReaderConfiguration* outConfig) override; - std::shared_ptr obtainPointerController( - int32_t /*deviceId*/) override; void notifyInputDevicesChanged(const std::vector& inputDevices) override; std::shared_ptr getKeyboardLayoutOverlay( const InputDeviceIdentifier&, const std::optional) override; @@ -97,7 +92,6 @@ private: std::condition_variable mDevicesChangedCondition; InputReaderConfiguration mConfig; - std::shared_ptr mPointerController; std::vector mInputDevices GUARDED_BY(mLock); bool mInputDevicesChanged GUARDED_BY(mLock){false}; std::vector mViewports; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index e1b46faac6..92489aec8d 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -46,7 +46,6 @@ #include #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" -#include "FakePointerController.h" #include "InputMapperTest.h" #include "InstrumentedInputReader.h" #include "TestConstants.h" @@ -1349,8 +1348,6 @@ protected: sp mFakePolicy; std::unique_ptr mReader; - std::shared_ptr mFakePointerController; - constexpr static auto EVENT_HAPPENED_TIMEOUT = 2000ms; constexpr static auto EVENT_DID_NOT_HAPPEN_TIMEOUT = 30ms; @@ -1359,8 +1356,6 @@ protected: GTEST_SKIP(); #endif mFakePolicy = sp::make(); - mFakePointerController = std::make_shared(); - mFakePolicy->setPointerController(mFakePointerController); setupInputReader(); } @@ -1654,8 +1649,6 @@ protected: } else { mFakePolicy->addInputUniqueIdAssociation(INPUT_PORT, UNIQUE_ID); } - mFakePointerController = std::make_shared(); - mFakePolicy->setPointerController(mFakePointerController); InputReaderIntegrationTest::setupInputReader(); diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index db8916880a..6389cdc5fb 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -54,10 +53,6 @@ public: MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode), (override)); - MOCK_METHOD(void, fadePointer, (), (override)); - MOCK_METHOD(std::shared_ptr, getPointerController, - (int32_t deviceId), (override)); - MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override)); int32_t bumpGeneration() override { return ++mGeneration; } diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index b44529bd04..031b77d67a 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -72,8 +72,6 @@ protected: } void testPointerVisibilityForKeys(const std::vector& keyCodes, bool expectVisible) { - EXPECT_CALL(mMockInputReaderContext, fadePointer) - .Times(expectVisible ? 0 : keyCodes.size()); for (int32_t keyCode : keyCodes) { process(EV_KEY, keyCode, 1); process(EV_SYN, SYN_REPORT, 0); @@ -84,7 +82,6 @@ protected: void testTouchpadTapStateForKeys(const std::vector& keyCodes, const bool expectPrevent) { - EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).Times(keyCodes.size()); if (expectPrevent) { EXPECT_CALL(mMockInputReaderContext, setPreventingTouchpadTaps(true)) .Times(keyCodes.size()); diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 2602ebbd14..e020ca9d62 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -258,57 +258,16 @@ public: void sysfsNodeChanged(const std::string& sysfsNodePath) override {} }; -class FuzzPointerController : public PointerControllerInterface { - std::shared_ptr mFdp; - -public: - FuzzPointerController(std::shared_ptr mFdp) : mFdp(mFdp) {} - ~FuzzPointerController() {} - std::optional getBounds() const override { - if (mFdp->ConsumeBool()) { - return {}; - } else { - return FloatRect{mFdp->ConsumeFloatingPoint(), - mFdp->ConsumeFloatingPoint(), - mFdp->ConsumeFloatingPoint(), - mFdp->ConsumeFloatingPoint()}; - } - } - void move(float deltaX, float deltaY) override {} - void setPosition(float x, float y) override {} - FloatPoint getPosition() const override { - return {mFdp->ConsumeFloatingPoint(), mFdp->ConsumeFloatingPoint()}; - } - void fade(Transition transition) override {} - void unfade(Transition transition) override {} - void setPresentation(Presentation presentation) override {} - void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) override {} - void clearSpots() override {} - int32_t getDisplayId() const override { return mFdp->ConsumeIntegral(); } - void setDisplayViewport(const DisplayViewport& displayViewport) override {} - void updatePointerIcon(PointerIconStyle iconId) override {} - void setCustomPointerIcon(const SpriteIcon& icon) override {} - void setSkipScreenshot(int32_t displayId, bool skip) override{}; - std::string dump() override { return ""; } -}; - class FuzzInputReaderPolicy : public InputReaderPolicyInterface { TouchAffineTransformation mTransform; - std::shared_ptr mPointerController; std::shared_ptr mFdp; protected: ~FuzzInputReaderPolicy() {} public: - FuzzInputReaderPolicy(std::shared_ptr mFdp) : mFdp(mFdp) { - mPointerController = std::make_shared(mFdp); - } + FuzzInputReaderPolicy(std::shared_ptr mFdp) : mFdp(mFdp) {} void getReaderConfiguration(InputReaderConfiguration* outConfig) override {} - std::shared_ptr obtainPointerController(int32_t deviceId) override { - return mPointerController; - } void notifyInputDevicesChanged(const std::vector& inputDevices) override {} std::shared_ptr getKeyboardLayoutOverlay( const InputDeviceIdentifier& identifier, @@ -360,10 +319,6 @@ public: bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override { return mFdp->ConsumeBool(); } - void fadePointer() override {} - std::shared_ptr getPointerController(int32_t deviceId) override { - return mPolicy->obtainPointerController(0); - } void requestTimeoutAtTime(nsecs_t when) override {} int32_t bumpGeneration() override { return mFdp->ConsumeIntegral(); } void getExternalStylusDevices(std::vector& outDevices) override {} -- GitLab From 9251d5bbcaca7ad8cc8417f6b5e9fd72666309b2 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 3 May 2024 23:29:37 +0000 Subject: [PATCH 318/465] PointerController: Cleanup after PointerChoreogpraher refactor Bug: 311416205 Test: atest inputflinger_tests Change-Id: I3e18d4ec258f373a3b59339e71c6cca30d983db3 --- services/inputflinger/include/PointerControllerInterface.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index 6b26e81637..c1467b3eed 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -61,8 +61,6 @@ public: * TODO(b/293587049): Refactor the PointerController class into different controller types. */ enum class ControllerType { - // The PointerController that is responsible for drawing all icons. - LEGACY, // Represents a single mouse pointer. MOUSE, // Represents multiple touch spots. -- GitLab From b1b93a030e0bb6d23acd3caa32b9e7e7d9ab2936 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 6 May 2024 16:13:29 +0000 Subject: [PATCH 319/465] Remove enable_pointer_choreographer flags that has completed rollout Bug: 311416205 Test: build, presubmit Change-Id: Ie65617b93a3452d9c2a56478fdc7ced930380dc6 --- libs/input/input_flags.aconfig | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 5c4b88912c..560166c2bd 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -15,13 +15,6 @@ flag { bug: "271455682" } -flag { - name: "enable_pointer_choreographer" - namespace: "input" - description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons" - bug: "293587049" -} - flag { name: "enable_gestures_library_timer_provider" namespace: "input" -- GitLab From 2ffa36b70412a84873e67a6d31123bafdafc9b78 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Wed, 8 May 2024 03:51:46 +0000 Subject: [PATCH 320/465] Refactor RenderAreaFuture to use RenderAreaBuilder Cleans up renderArea logic and allows all the work that needs to be done on the main thread in the same place. This will aid in the effort to reduce the number of hops to the SF main thread during screenshots. Bug: b/294936197 Test: atest SurfaceFlinger_test Test: presubmit Change-Id: I4234b49638aaecceb8d1fcff7f5cd43698b6c47f --- .../surfaceflinger/RegionSamplingThread.cpp | 18 ++-- services/surfaceflinger/SurfaceFlinger.cpp | 95 +++++++++---------- services/surfaceflinger/SurfaceFlinger.h | 17 ++-- .../tests/unittests/TestableSurfaceFlinger.h | 2 +- 4 files changed, 65 insertions(+), 67 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 2ec20ad5c2..2b4e234604 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -279,14 +279,6 @@ void RegionSamplingThread::captureSample() { const Rect sampledBounds = sampleRegion.bounds(); constexpr bool kHintForSeamlessTransition = false; - SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] { - DisplayRenderAreaBuilder displayRenderArea(sampledBounds, sampledBounds.getSize(), - ui::Dataspace::V0_SRGB, - kHintForSeamlessTransition, - true /* captureSecureLayers */, displayWeak); - return displayRenderArea.build(); - }); - std::unordered_set, SpHash> listeners; auto layerFilterFn = [&](const char* layerName, uint32_t layerId, const Rect& bounds, @@ -381,8 +373,14 @@ void RegionSamplingThread::captureSample() { constexpr bool kIsProtected = false; if (const auto fenceResult = - mFlinger.captureScreenshot(std::move(renderAreaFuture), getLayerSnapshots, buffer, - kRegionSampling, kGrayscale, kIsProtected, nullptr) + mFlinger.captureScreenshot(SurfaceFlinger::RenderAreaBuilderVariant( + std::in_place_type, + sampledBounds, sampledBounds.getSize(), + ui::Dataspace::V0_SRGB, + kHintForSeamlessTransition, + true /* captureSecureLayers */, displayWeak), + getLayerSnapshots, buffer, kRegionSampling, kGrayscale, + kIsProtected, nullptr) .get(); fenceResult.ok()) { fenceResult.value()->waitForever(LOG_TAG); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 64d80d9bab..0369549d01 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7892,13 +7892,6 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, } } - RenderAreaFuture renderAreaFuture = ftl::defer([=] { - DisplayRenderAreaBuilder displayRenderArea(args.sourceCrop, reqSize, args.dataspace, - args.hintForSeamlessTransition, - args.captureSecureLayers, displayWeak); - return displayRenderArea.build(); - }); - GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { getLayerSnapshots = @@ -7911,8 +7904,12 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); } - captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, - args.allowProtected, args.grayscale, captureListener); + captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, + args.sourceCrop, reqSize, args.dataspace, + args.hintForSeamlessTransition, + args.captureSecureLayers, displayWeak), + getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, + args.grayscale, captureListener); } void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args, @@ -7948,13 +7945,6 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args return; } - RenderAreaFuture renderAreaFuture = ftl::defer([=] { - DisplayRenderAreaBuilder displayRenderArea(Rect(), size, args.dataspace, - args.hintForSeamlessTransition, - false /* captureSecureLayers */, displayWeak); - return displayRenderArea.build(); - }); - GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, @@ -7975,8 +7965,12 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args constexpr bool kAllowProtected = false; constexpr bool kGrayscale = false; - captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, args.pixelFormat, - kAllowProtected, kGrayscale, captureListener); + captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, + Rect(), size, args.dataspace, + args.hintForSeamlessTransition, + false /* captureSecureLayers */, displayWeak), + getLayerSnapshots, size, args.pixelFormat, kAllowProtected, kGrayscale, + captureListener); } ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) { @@ -8057,22 +8051,6 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return; } - RenderAreaFuture renderAreaFuture = ftl::defer( - [=, this]() FTL_FAKE_GUARD(kMainThreadContext) -> std::unique_ptr { - LayerRenderAreaBuilder layerRenderArea(crop, reqSize, dataspace, - args.captureSecureLayers, - args.hintForSeamlessTransition, parent, - args.childrenOnly); - - frontend::LayerSnapshot* snapshot = - mLayerSnapshotBuilder.getSnapshot(parent->getSequence()); - if (!snapshot) { - ALOGW("Couldn't find layer snapshot for %d", parent->getSequence()); - } else { - layerRenderArea.setLayerInfo(snapshot); - } - return layerRenderArea.build(); - }); GetLayerSnapshotsFunction getLayerSnapshots; if (mLayerLifecycleManagerEnabled) { std::optional parentCrop = std::nullopt; @@ -8115,8 +8093,12 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return; } - captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat, - args.allowProtected, args.grayscale, captureListener); + captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, crop, + reqSize, dataspace, args.captureSecureLayers, + args.hintForSeamlessTransition, parent, + args.childrenOnly), + getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, + args.grayscale, captureListener); } // Creates a Future release fence for a layer and keeps track of it in a list to @@ -8143,7 +8125,7 @@ bool SurfaceFlinger::layersHasProtectedLayer( return protectedLayerFound; } -void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture, +void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshots, ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale, @@ -8192,21 +8174,35 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture, renderengine::impl::ExternalTexture::Usage:: WRITEABLE); auto futureFence = - captureScreenshot(std::move(renderAreaFuture), getLayerSnapshots, texture, + captureScreenshot(renderAreaBuilder, getLayerSnapshots, texture, false /* regionSampling */, grayscale, isProtected, captureListener); futureFence.get(); } ftl::SharedFuture SurfaceFlinger::captureScreenshot( - RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots, + RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, const sp& captureListener) { ATRACE_CALL(); - auto takeScreenshotFn = [=, this, renderAreaFuture = std::move(renderAreaFuture)]() REQUIRES( + auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES( kMainThreadContext) mutable -> ftl::SharedFuture { // LayerSnapshots must be obtained from the main thread. auto layers = getLayerSnapshots(); + + if (auto* layerRenderAreaBuilder = + std::get_if(&renderAreaBuilder)) { + // LayerSnapshotBuilder should only be accessed from the main thread. + frontend::LayerSnapshot* snapshot = + mLayerSnapshotBuilder.getSnapshot(layerRenderAreaBuilder->layer->getSequence()); + if (!snapshot) { + ALOGW("Couldn't find layer snapshot for %d", + layerRenderAreaBuilder->layer->getSequence()); + } else { + layerRenderAreaBuilder->setLayerInfo(snapshot); + } + } + if (FlagManager::getInstance().ce_fence_promise()) { for (auto& [layer, layerFE] : layers) { attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); @@ -8214,7 +8210,10 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( } ScreenCaptureResults captureResults; - std::shared_ptr renderArea = renderAreaFuture.get(); + std::unique_ptr renderArea = + std::visit([](auto&& arg) -> std::unique_ptr { return arg.build(); }, + renderAreaBuilder); + if (!renderArea) { ALOGW("Skipping screen capture because of invalid render area."); if (captureListener) { @@ -8225,8 +8224,8 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( } ftl::SharedFuture renderFuture = - renderScreenImpl(renderArea, buffer, regionSampling, grayscale, isProtected, - captureResults, layers); + renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, + isProtected, captureResults, layers); if (captureListener) { // Defer blocking on renderFuture back to the Binder thread. @@ -8243,9 +8242,7 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( }; // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number - // of calls on the main thread. renderAreaFuture runs on the main thread and should - // no longer be a future, so that it does not need to make an additional jump on the - // main thread whenever get() is called. + // of calls on the main thread. auto future = mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn))); @@ -8258,16 +8255,16 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( } ftl::SharedFuture SurfaceFlinger::renderScreenImpl( - std::shared_ptr renderArea, GetLayerSnapshotsFunction getLayerSnapshots, + std::unique_ptr renderArea, GetLayerSnapshotsFunction getLayerSnapshots, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) { auto layers = getLayerSnapshots(); - return renderScreenImpl(renderArea, buffer, regionSampling, grayscale, isProtected, + return renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected, captureResults, layers); } ftl::SharedFuture SurfaceFlinger::renderScreenImpl( - std::shared_ptr renderArea, + std::unique_ptr renderArea, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults, std::vector>>& layers) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index fc50fd97f8..af8f3150c8 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -192,6 +192,9 @@ enum class LatchUnsignaledConfig { Always, }; +struct DisplayRenderAreaBuilder; +struct LayerRenderAreaBuilder; + using DisplayColorSetting = compositionengine::OutputColorSetting; class SurfaceFlinger : public BnSurfaceComposer, @@ -383,7 +386,7 @@ private: using TransactionSchedule = scheduler::TransactionSchedule; using GetLayerSnapshotsFunction = std::function>>()>; - using RenderAreaFuture = ftl::Future>; + using RenderAreaBuilderVariant = std::variant; using DumpArgs = Vector; using Dumper = std::function; @@ -891,12 +894,12 @@ private: // Checks if a protected layer exists in a list of layers. bool layersHasProtectedLayer(const std::vector>>& layers) const; - void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize, - ui::PixelFormat, bool allowProtected, bool grayscale, - const sp&); + void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction, + ui::Size bufferSize, ui::PixelFormat, bool allowProtected, + bool grayscale, const sp&); ftl::SharedFuture captureScreenshot( - RenderAreaFuture, GetLayerSnapshotsFunction, + RenderAreaBuilderVariant, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, const sp&); @@ -904,13 +907,13 @@ private: // not yet been captured, and thus cannot yet be passed in as a parameter. // Needed for TestableSurfaceFlinger. ftl::SharedFuture renderScreenImpl( - std::shared_ptr, GetLayerSnapshotsFunction, + std::unique_ptr, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); ftl::SharedFuture renderScreenImpl( - std::shared_ptr, + std::unique_ptr, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&, std::vector>>& layers) EXCLUDES(mStateLock) diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 935346712f..d3b6e174cb 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -475,7 +475,7 @@ public: return mFlinger->setPowerModeInternal(display, mode); } - auto renderScreenImpl(std::shared_ptr renderArea, + auto renderScreenImpl(std::unique_ptr renderArea, SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers, const std::shared_ptr& buffer, bool regionSampling) { -- GitLab From 13bf76a87d9113d60f39e645c5453bf18d0a158d Mon Sep 17 00:00:00 2001 From: Linnan Li Date: Sun, 5 May 2024 19:18:02 +0800 Subject: [PATCH 321/465] Use a strongly typed LogicalDisplayId for displayId(2/n) Currently, we use int32_t for displayId, which is not a safe type, and it may also lead to misdefinition of types. Here, we introduce LogicalDisplayId as a strong type for displayId and move all contents of constants.h into LogicalDisplayId.h. Bug: 339106983 Test: atest inputflinger_tests Test: atest InputTests Test: m checkinput Test: m libsurfaceflinger_unittest Test: presubmit Change-Id: If44e56f69553d095af5adb59b595e4a852ab32ce Signed-off-by: Linnan Li --- include/input/DisplayViewport.h | 16 +- include/input/Input.h | 15 +- include/input/InputDevice.h | 6 +- include/input/InputEventBuilders.h | 9 +- include/input/InputTransport.h | 13 +- libs/gui/DisplayInfo.cpp | 8 +- libs/gui/WindowInfo.cpp | 8 +- libs/gui/include/gui/DisplayInfo.h | 4 +- libs/gui/include/gui/WindowInfo.h | 4 +- libs/gui/include/gui/constants.h | 37 --- libs/gui/tests/DisplayInfo_test.cpp | 2 +- libs/gui/tests/EndToEndNativeInputTest.cpp | 198 +++++++------- libs/gui/tests/WindowInfo_test.cpp | 2 +- libs/input/Input.cpp | 43 ++-- libs/input/InputConsumer.cpp | 23 +- libs/input/InputConsumerNoResampling.cpp | 12 +- libs/input/InputDevice.cpp | 6 +- libs/input/InputTransport.cpp | 20 +- libs/input/KeyCharacterMap.cpp | 7 +- libs/input/tests/InputEvent_test.cpp | 9 +- ...tPublisherAndConsumerNoResampling_test.cpp | 26 +- .../tests/InputPublisherAndConsumer_test.cpp | 37 ++- libs/input/tests/MotionPredictor_test.cpp | 5 +- libs/input/tests/TouchResampling_test.cpp | 3 +- libs/input/tests/VelocityTracker_test.cpp | 5 +- libs/input/tests/VerifiedInputEvent_test.cpp | 12 +- libs/ui/include/ui/LogicalDisplayId.h | 56 ++++ .../inputflinger/InputCommonConverter.cpp | 2 +- services/inputflinger/InputFilter.cpp | 2 +- .../inputflinger/InputFilterCallbacks.cpp | 6 +- services/inputflinger/InputReaderBase.cpp | 4 +- services/inputflinger/NotifyArgs.cpp | 4 +- .../inputflinger/PointerChoreographer.cpp | 74 +++--- services/inputflinger/PointerChoreographer.h | 52 ++-- .../benchmarks/InputDispatcher_benchmarks.cpp | 7 +- .../dispatcher/CancelationOptions.h | 2 +- services/inputflinger/dispatcher/Entry.cpp | 46 ++-- services/inputflinger/dispatcher/Entry.h | 25 +- .../inputflinger/dispatcher/FocusResolver.cpp | 26 +- .../inputflinger/dispatcher/FocusResolver.h | 22 +- .../dispatcher/InputDispatcher.cpp | 243 ++++++++++-------- .../inputflinger/dispatcher/InputDispatcher.h | 83 +++--- .../inputflinger/dispatcher/InputState.cpp | 3 +- services/inputflinger/dispatcher/InputState.h | 6 +- .../inputflinger/dispatcher/InputTarget.h | 1 - .../include/InputDispatcherInterface.h | 23 +- .../include/InputDispatcherPolicyInterface.h | 8 +- .../trace/AndroidInputEventProtoConverter.cpp | 4 +- .../trace/InputTracingBackendInterface.h | 4 +- .../inputflinger/include/InputReaderBase.h | 11 +- services/inputflinger/include/NotifyArgs.h | 22 +- .../inputflinger/include/NotifyArgsBuilders.h | 9 +- .../PointerChoreographerPolicyInterface.h | 3 +- .../include/PointerControllerInterface.h | 6 +- services/inputflinger/reader/InputDevice.cpp | 6 +- services/inputflinger/reader/InputReader.cpp | 7 +- .../inputflinger/reader/include/InputDevice.h | 2 +- .../inputflinger/reader/include/InputReader.h | 2 +- .../mapper/CapturedTouchpadEventConverter.cpp | 3 +- .../reader/mapper/CursorInputMapper.cpp | 13 +- .../reader/mapper/CursorInputMapper.h | 4 +- .../inputflinger/reader/mapper/InputMapper.h | 2 +- .../reader/mapper/JoystickInputMapper.cpp | 2 +- .../reader/mapper/KeyboardInputMapper.cpp | 6 +- .../reader/mapper/KeyboardInputMapper.h | 4 +- .../mapper/RotaryEncoderInputMapper.cpp | 2 +- .../mapper/TouchCursorInputMapperCommon.cpp | 4 +- .../mapper/TouchCursorInputMapperCommon.h | 2 +- .../reader/mapper/TouchInputMapper.cpp | 23 +- .../reader/mapper/TouchInputMapper.h | 12 +- .../reader/mapper/TouchpadInputMapper.cpp | 9 +- .../reader/mapper/TouchpadInputMapper.h | 4 +- .../reader/mapper/gestures/GestureConverter.h | 4 +- .../tests/CursorInputMapper_test.cpp | 6 +- .../tests/FakeInputDispatcherPolicy.cpp | 6 +- .../tests/FakeInputDispatcherPolicy.h | 8 +- .../tests/FakeInputReaderPolicy.cpp | 12 +- .../tests/FakeInputReaderPolicy.h | 6 +- .../tests/FakePointerController.cpp | 17 +- .../tests/FakePointerController.h | 21 +- services/inputflinger/tests/FakeWindows.cpp | 14 +- services/inputflinger/tests/FakeWindows.h | 44 ++-- .../inputflinger/tests/FocusResolver_test.cpp | 45 ++-- .../tests/GestureConverter_test.cpp | 120 ++++----- .../InputDeviceMetricsCollector_test.cpp | 3 +- .../tests/InputDispatcher_test.cpp | 153 +++++------ .../inputflinger/tests/InputMapperTest.cpp | 4 +- services/inputflinger/tests/InputMapperTest.h | 2 +- .../tests/InputProcessorConverter_test.cpp | 3 +- .../tests/InputProcessor_test.cpp | 5 +- .../inputflinger/tests/InputReader_test.cpp | 55 ++-- .../inputflinger/tests/InputTracingTest.cpp | 4 +- .../tests/LatencyTracker_test.cpp | 3 +- .../tests/MultiTouchInputMapper_test.cpp | 2 +- .../inputflinger/tests/NotifyArgs_test.cpp | 2 +- .../tests/PointerChoreographer_test.cpp | 183 +++++++------ .../tests/PreferStylusOverTouch_test.cpp | 8 +- .../inputflinger/tests/TestEventMatchers.h | 6 +- .../tests/TouchpadInputMapper_test.cpp | 2 +- .../tests/UnwantedInteractionBlocker_test.cpp | 8 +- .../tests/fuzzers/FuzzedInputStream.h | 2 +- .../tests/fuzzers/InputClassifierFuzzer.cpp | 2 +- .../tests/fuzzers/InputDispatcherFuzzer.cpp | 13 +- .../tests/fuzzers/InputReaderFuzzer.cpp | 5 +- .../tests/fuzzers/MapperHelpers.h | 2 +- services/surfaceflinger/DisplayDevice.cpp | 2 +- .../surfaceflinger/FrontEnd/DisplayInfo.h | 1 + .../FrontEnd/LayerSnapshotBuilder.cpp | 3 +- services/surfaceflinger/Layer.cpp | 8 +- services/surfaceflinger/LayerProtoHelper.cpp | 2 +- .../Tracing/TransactionProtoParser.cpp | 4 +- .../unittests/TransactionProtoParserTest.cpp | 2 +- 112 files changed, 1147 insertions(+), 1036 deletions(-) delete mode 100644 libs/gui/include/gui/constants.h create mode 100644 libs/ui/include/ui/LogicalDisplayId.h diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h index b0eceefba0..97c2ab8451 100644 --- a/include/input/DisplayViewport.h +++ b/include/input/DisplayViewport.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -47,7 +46,7 @@ enum class ViewportType : int32_t { * See com.android.server.display.DisplayViewport. */ struct DisplayViewport { - int32_t displayId; // -1 if invalid + ui::LogicalDisplayId displayId; // ADISPLAY_ID_NONE if invalid ui::Rotation orientation; int32_t logicalLeft; int32_t logicalTop; @@ -67,7 +66,7 @@ struct DisplayViewport { ViewportType type; DisplayViewport() - : displayId(ADISPLAY_ID_NONE), + : displayId(ui::ADISPLAY_ID_NONE), orientation(ui::ROTATION_0), logicalLeft(0), logicalTop(0), @@ -99,12 +98,10 @@ struct DisplayViewport { return !(*this == other); } - inline bool isValid() const { - return displayId >= 0; - } + inline bool isValid() const { return displayId.isValid(); } void setNonDisplayViewport(int32_t width, int32_t height) { - displayId = ADISPLAY_ID_NONE; + displayId = ui::ADISPLAY_ID_NONE; orientation = ui::ROTATION_0; logicalLeft = 0; logicalTop = 0; @@ -123,12 +120,13 @@ struct DisplayViewport { } std::string toString() const { - return StringPrintf("Viewport %s: displayId=%d, uniqueId=%s, port=%s, orientation=%d, " + return StringPrintf("Viewport %s: displayId=%s, uniqueId=%s, port=%s, orientation=%d, " "logicalFrame=[%d, %d, %d, %d], " "physicalFrame=[%d, %d, %d, %d], " "deviceSize=[%d, %d], " "isActive=[%d]", - ftl::enum_string(type).c_str(), displayId, uniqueId.c_str(), + ftl::enum_string(type).c_str(), displayId.toString().c_str(), + uniqueId.c_str(), physicalPort ? ftl::to_string(*physicalPort).c_str() : "", static_cast(orientation), logicalLeft, logicalTop, logicalRight, logicalBottom, physicalLeft, physicalTop, physicalRight, physicalBottom, diff --git a/include/input/Input.h b/include/input/Input.h index 3f81fb05fd..092a6982ed 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -547,9 +548,9 @@ public: inline void setSource(uint32_t source) { mSource = source; } - inline int32_t getDisplayId() const { return mDisplayId; } + inline ui::LogicalDisplayId getDisplayId() const { return mDisplayId; } - inline void setDisplayId(int32_t displayId) { mDisplayId = displayId; } + inline void setDisplayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; } inline std::array getHmac() const { return mHmac; } @@ -558,7 +559,7 @@ public: bool operator==(const InputEvent&) const = default; protected: - void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId, + void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId, std::array hmac); void initialize(const InputEvent& from); @@ -566,7 +567,7 @@ protected: int32_t mId; DeviceId mDeviceId; uint32_t mSource; - int32_t mDisplayId; + ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_NONE}; std::array mHmac; }; @@ -602,7 +603,7 @@ public: static const char* getLabel(int32_t keyCode); static std::optional getKeyCodeFromLabel(const char* label); - void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId, + void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId, std::array hmac, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime); @@ -864,7 +865,7 @@ public: ssize_t findPointerIndex(int32_t pointerId) const; - void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId, + void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId, std::array hmac, int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState, MotionClassification classification, const ui::Transform& transform, @@ -1073,7 +1074,7 @@ struct __attribute__((__packed__)) VerifiedInputEvent { DeviceId deviceId; nsecs_t eventTimeNanos; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId; }; /** diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index e93fe8c2dd..8783d9c192 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -280,7 +280,7 @@ public: void initialize(int32_t id, int32_t generation, int32_t controllerNumber, const InputDeviceIdentifier& identifier, const std::string& alias, - bool isExternal, bool hasMic, int32_t associatedDisplayId, + bool isExternal, bool hasMic, ui::LogicalDisplayId associatedDisplayId, InputDeviceViewBehavior viewBehavior = {{}}, bool enabled = true); inline int32_t getId() const { return mId; } @@ -348,7 +348,7 @@ public: } inline std::optional getUsiVersion() const { return mUsiVersion; } - inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; } + inline ui::LogicalDisplayId getAssociatedDisplayId() const { return mAssociatedDisplayId; } inline void setEnabled(bool enabled) { mEnabled = enabled; } inline bool isEnabled() const { return mEnabled; } @@ -366,7 +366,7 @@ private: int32_t mKeyboardType; std::shared_ptr mKeyCharacterMap; std::optional mUsiVersion; - int32_t mAssociatedDisplayId; + ui::LogicalDisplayId mAssociatedDisplayId{ui::ADISPLAY_ID_NONE}; bool mEnabled; bool mHasVibrator; diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h index c0c5e2412d..837e70e114 100644 --- a/include/input/InputEventBuilders.h +++ b/include/input/InputEventBuilders.h @@ -18,7 +18,6 @@ #include #include -#include #include #include // for nsecs_t, systemTime @@ -83,7 +82,7 @@ public: return *this; } - MotionEventBuilder& displayId(int32_t displayId) { + MotionEventBuilder& displayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; return *this; } @@ -159,7 +158,7 @@ private: int32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; int32_t mActionButton{0}; int32_t mButtonState{0}; int32_t mFlags{0}; @@ -209,7 +208,7 @@ public: return *this; } - KeyEventBuilder& displayId(int32_t displayId) { + KeyEventBuilder& displayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; return *this; } @@ -248,7 +247,7 @@ private: uint32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mFlags{0}; int32_t mKeyCode{AKEYCODE_UNKNOWN}; diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h index 5f9c8f5a5c..6548810ca8 100644 --- a/include/input/InputTransport.h +++ b/include/input/InputTransport.h @@ -353,9 +353,10 @@ public: * Other errors probably indicate that the channel is broken. */ status_t publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, - int32_t displayId, std::array hmac, int32_t action, - int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, - int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime); + ui::LogicalDisplayId displayId, std::array hmac, + int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, int32_t repeatCount, nsecs_t downTime, + nsecs_t eventTime); /* Publishes a motion event to the input channel. * @@ -366,9 +367,9 @@ public: * Other errors probably indicate that the channel is broken. */ status_t publishMotionEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, - int32_t displayId, std::array hmac, int32_t action, - int32_t actionButton, int32_t flags, int32_t edgeFlags, - int32_t metaState, int32_t buttonState, + ui::LogicalDisplayId displayId, std::array hmac, + int32_t action, int32_t actionButton, int32_t flags, + int32_t edgeFlags, int32_t metaState, int32_t buttonState, MotionClassification classification, const ui::Transform& transform, float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition, const ui::Transform& rawTransform, diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp index bd640df81e..47cec0778e 100644 --- a/libs/gui/DisplayInfo.cpp +++ b/libs/gui/DisplayInfo.cpp @@ -37,8 +37,9 @@ status_t DisplayInfo::readFromParcel(const android::Parcel* parcel) { return BAD_VALUE; } + int32_t displayIdInt; float dsdx, dtdx, tx, dtdy, dsdy, ty; - SAFE_PARCEL(parcel->readInt32, &displayId); + SAFE_PARCEL(parcel->readInt32, &displayIdInt); SAFE_PARCEL(parcel->readInt32, &logicalWidth); SAFE_PARCEL(parcel->readInt32, &logicalHeight); SAFE_PARCEL(parcel->readFloat, &dsdx); @@ -48,6 +49,7 @@ status_t DisplayInfo::readFromParcel(const android::Parcel* parcel) { SAFE_PARCEL(parcel->readFloat, &dsdy); SAFE_PARCEL(parcel->readFloat, &ty); + displayId = ui::LogicalDisplayId{displayIdInt}; transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1}); return OK; @@ -59,7 +61,7 @@ status_t DisplayInfo::writeToParcel(android::Parcel* parcel) const { return BAD_VALUE; } - SAFE_PARCEL(parcel->writeInt32, displayId); + SAFE_PARCEL(parcel->writeInt32, displayId.val()); SAFE_PARCEL(parcel->writeInt32, logicalWidth); SAFE_PARCEL(parcel->writeInt32, logicalHeight); SAFE_PARCEL(parcel->writeFloat, transform.dsdx()); @@ -76,7 +78,7 @@ void DisplayInfo::dump(std::string& out, const char* prefix) const { using android::base::StringAppendF; out += prefix; - StringAppendF(&out, "DisplayViewport[id=%" PRId32 "]\n", displayId); + StringAppendF(&out, "DisplayViewport[id=%s]\n", displayId.toString().c_str()); out += prefix; StringAppendF(&out, INDENT "Width=%" PRId32 ", Height=%" PRId32 "\n", logicalWidth, logicalHeight); diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp index ad0d99d11e..82d2554340 100644 --- a/libs/gui/WindowInfo.cpp +++ b/libs/gui/WindowInfo.cpp @@ -146,7 +146,7 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const { parcel->writeInt32(ownerUid.val()) ?: parcel->writeUtf8AsUtf16(packageName) ?: parcel->writeInt32(inputConfig.get()) ?: - parcel->writeInt32(displayId) ?: + parcel->writeInt32(displayId.val()) ?: applicationInfo.writeToParcel(parcel) ?: parcel->write(touchableRegion) ?: parcel->writeBool(replaceTouchableRegionWithCrop) ?: @@ -175,7 +175,8 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) { } float dsdx, dtdx, tx, dtdy, dsdy, ty; - int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt; + int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt, + displayIdInt; sp touchableRegionCropHandleSp; // clang-format off @@ -198,7 +199,7 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) { parcel->readInt32(&ownerUidInt) ?: parcel->readUtf8FromUtf16(&packageName) ?: parcel->readInt32(&inputConfigInt) ?: - parcel->readInt32(&displayId) ?: + parcel->readInt32(&displayIdInt) ?: applicationInfo.readFromParcel(parcel) ?: parcel->read(touchableRegion) ?: parcel->readBool(&replaceTouchableRegionWithCrop) ?: @@ -221,6 +222,7 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) { ownerPid = Pid{ownerPidInt}; ownerUid = Uid{static_cast(ownerUidInt)}; touchableRegionCropHandle = touchableRegionCropHandleSp; + displayId = ui::LogicalDisplayId{displayIdInt}; return OK; } diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h index 42b62c755c..7282b807a4 100644 --- a/libs/gui/include/gui/DisplayInfo.h +++ b/libs/gui/include/gui/DisplayInfo.h @@ -18,7 +18,7 @@ #include #include -#include +#include #include namespace android::gui { @@ -29,7 +29,7 @@ namespace android::gui { * This should only be used by InputFlinger to support raw coordinates in logical display space. */ struct DisplayInfo : public Parcelable { - int32_t displayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; // Logical display dimensions. int32_t logicalWidth = 0; diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index b73e497032..3ea3f67bd8 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -234,7 +234,7 @@ struct WindowInfo : public Parcelable { Uid ownerUid = Uid::INVALID; std::string packageName; ftl::Flags inputConfig; - int32_t displayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; InputApplicationInfo applicationInfo; bool replaceTouchableRegionWithCrop = false; wp touchableRegionCropHandle; diff --git a/libs/gui/include/gui/constants.h b/libs/gui/include/gui/constants.h deleted file mode 100644 index 8eab3783e9..0000000000 --- a/libs/gui/include/gui/constants.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include - -namespace android { - -/** - * Invalid value for display size. Used when display size isn't available. - */ -constexpr int32_t INVALID_DISPLAY_SIZE = 0; - -enum { - /* Used when an event is not associated with any display. - * Typically used for non-pointer events. */ - ADISPLAY_ID_NONE = -1, - - /* The default display id. */ - ADISPLAY_ID_DEFAULT = 0, -}; - -} // namespace android \ No newline at end of file diff --git a/libs/gui/tests/DisplayInfo_test.cpp b/libs/gui/tests/DisplayInfo_test.cpp index df3329cd52..4df76b1591 100644 --- a/libs/gui/tests/DisplayInfo_test.cpp +++ b/libs/gui/tests/DisplayInfo_test.cpp @@ -28,7 +28,7 @@ namespace test { TEST(DisplayInfo, Parcelling) { DisplayInfo info; - info.displayId = 42; + info.displayId = ui::LogicalDisplayId{42}; info.logicalWidth = 99; info.logicalHeight = 78; info.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1}); diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index f441eaa95a..c0e79655f8 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -59,8 +59,16 @@ using android::gui::FocusRequest; using android::gui::InputApplicationInfo; using android::gui::TouchOcclusionMode; using android::gui::WindowInfo; +using android::ui::ADISPLAY_ID_DEFAULT; +using android::ui::ADISPLAY_ID_NONE; -namespace android::test { +namespace android { +namespace { +ui::LogicalDisplayId toDisplayId(ui::LayerStack layerStack) { + return ui::LogicalDisplayId{static_cast(layerStack.id)}; +} +} // namespace +namespace test { using Transaction = SurfaceComposerClient::Transaction; @@ -68,7 +76,9 @@ sp getInputFlinger() { sp input(defaultServiceManager()->waitForService(String16("inputflinger"))); if (input == nullptr) { ALOGE("Failed to link to input service"); - } else { ALOGE("Linked to input"); } + } else { + ALOGE("Linked to input"); + } return interface_cast(input); } @@ -99,7 +109,7 @@ private: class InputSurface { public: - InputSurface(const sp &sc, int width, int height, bool noInputChannel = false) { + InputSurface(const sp& sc, int width, int height, bool noInputChannel = false) { mSurfaceControl = sc; mInputFlinger = getInputFlinger(); @@ -130,7 +140,7 @@ public: mInputInfo.applicationInfo = aInfo; } - static std::unique_ptr makeColorInputSurface(const sp &scc, + static std::unique_ptr makeColorInputSurface(const sp& scc, int width, int height) { sp surfaceControl = scc->createSurface(String8("Test Surface"), 0 /* bufHeight */, 0 /* bufWidth */, @@ -140,7 +150,7 @@ public: } static std::unique_ptr makeBufferInputSurface( - const sp &scc, int width, int height) { + const sp& scc, int width, int height) { sp surfaceControl = scc->createSurface(String8("Test Buffer Surface"), width, height, PIXEL_FORMAT_RGBA_8888, 0 /* flags */); @@ -148,7 +158,7 @@ public: } static std::unique_ptr makeContainerInputSurface( - const sp &scc, int width, int height) { + const sp& scc, int width, int height) { sp surfaceControl = scc->createSurface(String8("Test Container Surface"), 0 /* bufHeight */, 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888, @@ -157,7 +167,7 @@ public: } static std::unique_ptr makeContainerInputSurfaceNoInputChannel( - const sp &scc, int width, int height) { + const sp& scc, int width, int height) { sp surfaceControl = scc->createSurface(String8("Test Container Surface"), 100 /* height */, 100 /* width */, PIXEL_FORMAT_RGBA_8888, @@ -167,7 +177,7 @@ public: } static std::unique_ptr makeCursorInputSurface( - const sp &scc, int width, int height) { + const sp& scc, int width, int height) { sp surfaceControl = scc->createSurface(String8("Test Cursor Surface"), 0 /* bufHeight */, 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888, @@ -178,7 +188,7 @@ public: InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) { mClientChannel->waitForMessage(timeout); - InputEvent *ev; + InputEvent* ev; uint32_t seqId; status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev); if (consumed != OK) { @@ -190,10 +200,10 @@ public: } void assertFocusChange(bool hasFocus) { - InputEvent *ev = consumeEvent(); + InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::FOCUS, ev->getType()); - FocusEvent *focusEvent = static_cast(ev); + FocusEvent* focusEvent = static_cast(ev); EXPECT_EQ(hasFocus, focusEvent->getHasFocus()); } @@ -216,10 +226,10 @@ public: } void expectTapWithFlag(int x, int y, int32_t flags) { - InputEvent *ev = consumeEvent(); + InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::MOTION, ev->getType()); - MotionEvent *mev = static_cast(ev); + MotionEvent* mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); EXPECT_EQ(x, mev->getX(0)); EXPECT_EQ(y, mev->getY(0)); @@ -228,18 +238,18 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::MOTION, ev->getType()); - mev = static_cast(ev); + mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(flags, mev->getFlags() & flags); } void expectTapInDisplayCoordinates(int displayX, int displayY) { - InputEvent *ev = consumeEvent(); + InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::MOTION, ev->getType()); - MotionEvent *mev = static_cast(ev); + MotionEvent* mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction()); - const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/); + const PointerCoords& coords = *mev->getRawPointerCoords(0 /*pointerIndex*/); EXPECT_EQ(displayX, coords.getX()); EXPECT_EQ(displayY, coords.getY()); EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); @@ -247,16 +257,16 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::MOTION, ev->getType()); - mev = static_cast(ev); + mev = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction()); EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS); } void expectKey(int32_t keycode) { - InputEvent *ev = consumeEvent(); + InputEvent* ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::KEY, ev->getType()); - KeyEvent *keyEvent = static_cast(ev); + KeyEvent* keyEvent = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction()); EXPECT_EQ(keycode, keyEvent->getKeyCode()); EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS); @@ -264,7 +274,7 @@ public: ev = consumeEvent(); ASSERT_NE(ev, nullptr); ASSERT_EQ(InputEventType::KEY, ev->getType()); - keyEvent = static_cast(ev); + keyEvent = static_cast(ev); EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction()); EXPECT_EQ(keycode, keyEvent->getKeyCode()); EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS); @@ -282,7 +292,7 @@ public: } virtual void doTransaction( - std::function &)> + std::function&)> transactionBody) { SurfaceComposerClient::Transaction t; transactionBody(t, mSurfaceControl); @@ -303,13 +313,13 @@ public: reportedListener->wait(); } - void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) { + void requestFocus(ui::LogicalDisplayId displayId = ADISPLAY_ID_DEFAULT) { SurfaceComposerClient::Transaction t; FocusRequest request; request.token = mInputInfo.token; request.windowName = mInputInfo.name; request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); - request.displayId = displayId; + request.displayId = displayId.val(); t.setFocusedWindow(request); t.apply(true); } @@ -327,7 +337,7 @@ public: class BlastInputSurface : public InputSurface { public: - BlastInputSurface(const sp &sc, const sp &parentSc, int width, + BlastInputSurface(const sp& sc, const sp& parentSc, int width, int height) : InputSurface(sc, width, height) { mParentSurfaceControl = parentSc; @@ -336,7 +346,7 @@ public: ~BlastInputSurface() = default; static std::unique_ptr makeBlastInputSurface( - const sp &scc, int width, int height) { + const sp& scc, int width, int height) { sp parentSc = scc->createSurface(String8("Test Parent Surface"), 0 /* bufHeight */, 0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888, @@ -351,7 +361,7 @@ public: } void doTransaction( - std::function &)> + std::function&)> transactionBody) override { SurfaceComposerClient::Transaction t; transactionBody(t, mParentSurfaceControl); @@ -378,9 +388,7 @@ private: class InputSurfacesTest : public ::testing::Test { public: - InputSurfacesTest() { - ProcessState::self()->startThreadPool(); - } + InputSurfacesTest() { ProcessState::self()->startThreadPool(); } void SetUp() { mComposerClient = new SurfaceComposerClient; @@ -400,15 +408,13 @@ public: mBufferPostDelay = static_cast(1e6 / mode.peakRefreshRate) * 3; } - void TearDown() { - mComposerClient->dispose(); - } + void TearDown() { mComposerClient->dispose(); } std::unique_ptr makeSurface(int width, int height) { return InputSurface::makeColorInputSurface(mComposerClient, width, height); } - void postBuffer(const sp &layer, int32_t w, int32_t h) { + void postBuffer(const sp& layer, int32_t w, int32_t h) { int64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN | BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE; sp buffer = @@ -421,11 +427,11 @@ public: int32_t mBufferPostDelay; }; -void injectTapOnDisplay(int x, int y, int displayId) { +void injectTapOnDisplay(int x, int y, ui::LogicalDisplayId displayId) { char *buf1, *buf2, *bufDisplayId; asprintf(&buf1, "%d", x); asprintf(&buf2, "%d", y); - asprintf(&bufDisplayId, "%d", displayId); + asprintf(&bufDisplayId, "%d", displayId.val()); if (fork() == 0) { execlp("input", "input", "-d", bufDisplayId, "tap", buf1, buf2, NULL); } @@ -435,10 +441,10 @@ void injectTap(int x, int y) { injectTapOnDisplay(x, y, ADISPLAY_ID_DEFAULT); } -void injectKeyOnDisplay(uint32_t keycode, int displayId) { +void injectKeyOnDisplay(uint32_t keycode, ui::LogicalDisplayId displayId) { char *buf1, *bufDisplayId; asprintf(&buf1, "%d", keycode); - asprintf(&bufDisplayId, "%d", displayId); + asprintf(&bufDisplayId, "%d", displayId.val()); if (fork() == 0) { execlp("input", "input", "-d", bufDisplayId, "keyevent", buf1, NULL); } @@ -476,12 +482,8 @@ TEST_F(InputSurfacesTest, input_respects_positioning) { injectTap(101, 101); surface->expectTap(1, 1); - surface2->doTransaction([](auto &t, auto &sc) { - t.setPosition(sc, 100, 100); - }); - surface->doTransaction([](auto &t, auto &sc) { - t.setPosition(sc, 200, 200); - }); + surface2->doTransaction([](auto& t, auto& sc) { t.setPosition(sc, 100, 100); }); + surface->doTransaction([](auto& t, auto& sc) { t.setPosition(sc, 200, 200); }); injectTap(101, 101); surface2->expectTap(1, 1); @@ -497,23 +499,17 @@ TEST_F(InputSurfacesTest, input_respects_layering) { surface->showAt(10, 10); surface2->showAt(10, 10); - surface->doTransaction([](auto &t, auto &sc) { - t.setLayer(sc, LAYER_BASE + 1); - }); + surface->doTransaction([](auto& t, auto& sc) { t.setLayer(sc, LAYER_BASE + 1); }); injectTap(11, 11); surface->expectTap(1, 1); - surface2->doTransaction([](auto &t, auto &sc) { - t.setLayer(sc, LAYER_BASE + 1); - }); + surface2->doTransaction([](auto& t, auto& sc) { t.setLayer(sc, LAYER_BASE + 1); }); injectTap(11, 11); surface2->expectTap(1, 1); - surface2->doTransaction([](auto &t, auto &sc) { - t.hide(sc); - }); + surface2->doTransaction([](auto& t, auto& sc) { t.hide(sc); }); injectTap(11, 11); surface->expectTap(1, 1); @@ -562,7 +558,7 @@ TEST_F(InputSurfacesTest, input_respects_cropped_surface_insets) { childSurface->mInputInfo.surfaceInset = 10; childSurface->showAt(100, 100); - childSurface->doTransaction([&](auto &t, auto &sc) { + childSurface->doTransaction([&](auto& t, auto& sc) { t.setPosition(sc, -5, -5); t.reparent(sc, parentSurface->mSurfaceControl); }); @@ -583,7 +579,7 @@ TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets) { fgSurface->mInputInfo.surfaceInset = 5; fgSurface->showAt(100, 100); - fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); }); + fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); }); // expect = touch / scale - inset injectTap(112, 124); @@ -602,7 +598,7 @@ TEST_F(InputSurfacesTest, input_respects_scaled_surface_insets_overflow) { fgSurface->mInputInfo.surfaceInset = INT32_MAX; fgSurface->showAt(100, 100); - fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); }); + fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); }); // expect no crash for overflow, and inset size to be clamped to surface size injectTap(112, 124); @@ -651,7 +647,7 @@ TEST_F(InputSurfacesTest, input_respects_scaled_touchable_region_overflow) { fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}); fgSurface->showAt(0, 0); - fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); }); + fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); }); // Expect no crash for overflow. injectTap(12, 24); @@ -661,7 +657,7 @@ TEST_F(InputSurfacesTest, input_respects_scaled_touchable_region_overflow) { // Ensure we ignore transparent region when getting screen bounds when positioning input frame. TEST_F(InputSurfacesTest, input_ignores_transparent_region) { std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { Region transparentRegion(Rect(0, 0, 10, 10)); t.setTransparentRegionHint(sc, transparentRegion); }); @@ -702,7 +698,7 @@ TEST_F(InputSurfacesTest, input_respects_buffer_layer_alpha) { injectTap(11, 11); bufferSurface->expectTap(1, 1); - bufferSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); }); + bufferSurface->doTransaction([](auto& t, auto& sc) { t.setAlpha(sc, 0.0); }); injectTap(11, 11); bgSurface->expectTap(1, 1); @@ -718,7 +714,7 @@ TEST_F(InputSurfacesTest, input_ignores_color_layer_alpha) { injectTap(11, 11); fgSurface->expectTap(1, 1); - fgSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); }); + fgSurface->doTransaction([](auto& t, auto& sc) { t.setAlpha(sc, 0.0); }); injectTap(11, 11); fgSurface->expectTap(1, 1); @@ -735,7 +731,7 @@ TEST_F(InputSurfacesTest, input_respects_container_layer_visiblity) { injectTap(11, 11); containerSurface->expectTap(1, 1); - containerSurface->doTransaction([](auto &t, auto &sc) { t.hide(sc); }); + containerSurface->doTransaction([](auto& t, auto& sc) { t.hide(sc); }); injectTap(11, 11); bgSurface->expectTap(1, 1); @@ -775,19 +771,19 @@ TEST_F(InputSurfacesTest, can_be_focused) { TEST_F(InputSurfacesTest, rotate_surface) { std::unique_ptr surface = makeSurface(100, 100); surface->showAt(10, 10); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, 0, 1, -1, 0); // 90 degrees }); injectTap(8, 11); surface->expectTap(1, 2); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, -1, 0, 0, -1); // 180 degrees }); injectTap(9, 8); surface->expectTap(1, 2); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, 0, -1, 1, 0); // 270 degrees }); injectTap(12, 9); @@ -797,19 +793,19 @@ TEST_F(InputSurfacesTest, rotate_surface) { TEST_F(InputSurfacesTest, rotate_surface_with_scale) { std::unique_ptr surface = makeSurface(100, 100); surface->showAt(10, 10); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, 0, 2, -4, 0); // 90 degrees }); injectTap(2, 12); surface->expectTap(1, 2); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, -2, 0, 0, -4); // 180 degrees }); injectTap(8, 2); surface->expectTap(1, 2); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, 0, -2, 4, 0); // 270 degrees }); injectTap(18, 8); @@ -821,19 +817,19 @@ TEST_F(InputSurfacesTest, rotate_surface_with_scale_and_insets) { surface->mInputInfo.surfaceInset = 5; surface->showAt(100, 100); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, 0, 2, -4, 0); // 90 degrees }); injectTap(40, 120); surface->expectTap(5, 10); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, -2, 0, 0, -4); // 180 degrees }); injectTap(80, 40); surface->expectTap(5, 10); - surface->doTransaction([](auto &t, auto &sc) { + surface->doTransaction([](auto& t, auto& sc) { t.setMatrix(sc, 0, -2, 4, 0); // 270 degrees }); injectTap(160, 80); @@ -874,7 +870,7 @@ TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) { nonTouchableSurface->showAt(0, 0); parentSurface->showAt(100, 100); - nonTouchableSurface->doTransaction([&](auto &t, auto &sc) { + nonTouchableSurface->doTransaction([&](auto& t, auto& sc) { t.setCrop(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50)); t.reparent(sc, parentSurface->mSurfaceControl); }); @@ -898,7 +894,7 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_crop) { nonTouchableSurface->showAt(0, 0); parentSurface->showAt(50, 50); - nonTouchableSurface->doTransaction([&](auto &t, auto &sc) { + nonTouchableSurface->doTransaction([&](auto& t, auto& sc) { t.setCrop(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50)); t.reparent(sc, parentSurface->mSurfaceControl); }); @@ -940,7 +936,7 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_blast) { TEST_F(InputSurfacesTest, strict_unobscured_input_unobscured_window) { std::unique_ptr surface = makeSurface(100, 100); surface->doTransaction( - [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); + [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); surface->showAt(100, 100); injectTap(101, 101); @@ -954,7 +950,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_unobscured_window) { TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) { std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { + surface->doTransaction([&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); t.setMatrix(sc, 2.0, 0, 0, 2.0); }); @@ -973,7 +969,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) { std::unique_ptr surface = makeSurface(100, 100); surface->mInputInfo.ownerUid = gui::Uid{11111}; surface->doTransaction( - [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); + [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); surface->showAt(100, 100); std::unique_ptr obscuringSurface = makeSurface(100, 100); obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); @@ -992,7 +988,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) { std::unique_ptr surface = makeSurface(100, 100); surface->mInputInfo.ownerUid = gui::Uid{11111}; surface->doTransaction( - [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); + [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); }); surface->showAt(100, 100); std::unique_ptr obscuringSurface = makeSurface(100, 100); obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true); @@ -1015,7 +1011,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) { std::unique_ptr surface = makeSurface(100, 100); surface->showAt(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { + surface->doTransaction([&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); t.reparent(sc, parentSurface->mSurfaceControl); t.setAlpha(parentSurface->mSurfaceControl, 0.9f); @@ -1036,7 +1032,7 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) { parentSurface->showAt(0, 0, Rect(0, 0, 300, 300)); std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { + surface->doTransaction([&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); t.reparent(sc, parentSurface->mSurfaceControl); t.setCrop(parentSurface->mSurfaceControl, Rect(10, 10, 100, 100)); @@ -1070,7 +1066,7 @@ TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) { TEST_F(InputSurfacesTest, drop_input_policy) { std::unique_ptr surface = makeSurface(100, 100); surface->doTransaction( - [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::ALL); }); + [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::ALL); }); surface->showAt(100, 100); injectTap(101, 101); @@ -1102,7 +1098,7 @@ TEST_F(InputSurfacesTest, cropped_container_replaces_touchable_region_with_null_ std::unique_ptr containerSurface = InputSurface::makeContainerInputSurface(mComposerClient, 100, 100); containerSurface->doTransaction( - [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); }); + [&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); }); containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true; containerSurface->mInputInfo.touchableRegionCropHandle = nullptr; parentContainer->showAt(10, 10, Rect(0, 0, 20, 20)); @@ -1127,7 +1123,7 @@ TEST_F(InputSurfacesTest, uncropped_container_replaces_touchable_region_with_nul std::unique_ptr containerSurface = InputSurface::makeContainerInputSurface(mComposerClient, 100, 100); containerSurface->doTransaction( - [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); }); + [&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); }); containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true; containerSurface->mInputInfo.touchableRegionCropHandle = nullptr; parentContainer->showAt(10, 10, Rect(0, 0, 20, 20)); @@ -1179,7 +1175,7 @@ TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { InputSurface::makeContainerInputSurfaceNoInputChannel(mComposerClient, 100, 100); childContainerSurface->showAt(0, 0); childContainerSurface->doTransaction( - [&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); }); + [&](auto& t, auto& sc) { t.reparent(sc, parent->mSurfaceControl); }); injectTap(101, 101); parent->assertNoEvent(); @@ -1188,8 +1184,9 @@ TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) { class MultiDisplayTests : public InputSurfacesTest { public: MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); } + void TearDown() override { - for (auto &token : mVirtualDisplays) { + for (auto& token : mVirtualDisplays) { SurfaceComposerClient::destroyDisplay(token); } InputSurfacesTest::TearDown(); @@ -1226,18 +1223,18 @@ TEST_F(MultiDisplayTests, drop_touch_if_layer_on_invalid_display) { ui::LayerStack layerStack = ui::LayerStack::fromValue(42); // Do not create a display associated with the LayerStack. std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); }); + surface->doTransaction([&](auto& t, auto& sc) { t.setLayerStack(sc, layerStack); }); surface->showAt(100, 100); // Touches should be dropped if the layer is on an invalid display. - injectTapOnDisplay(101, 101, layerStack.id); + injectTapOnDisplay(101, 101, toDisplayId(layerStack)); surface->assertNoEvent(); // However, we still let the window be focused and receive keys. - surface->requestFocus(layerStack.id); + surface->requestFocus(toDisplayId(layerStack)); surface->assertFocusChange(true); - injectKeyOnDisplay(AKEYCODE_V, layerStack.id); + injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack)); surface->expectKey(AKEYCODE_V); } @@ -1245,15 +1242,15 @@ TEST_F(MultiDisplayTests, virtual_display_receives_input) { ui::LayerStack layerStack = ui::LayerStack::fromValue(42); createDisplay(1000, 1000, false /*isSecure*/, layerStack); std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); }); + surface->doTransaction([&](auto& t, auto& sc) { t.setLayerStack(sc, layerStack); }); surface->showAt(100, 100); - injectTapOnDisplay(101, 101, layerStack.id); + injectTapOnDisplay(101, 101, toDisplayId(layerStack)); surface->expectTap(1, 1); - surface->requestFocus(layerStack.id); + surface->requestFocus(toDisplayId(layerStack)); surface->assertFocusChange(true); - injectKeyOnDisplay(AKEYCODE_V, layerStack.id); + injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack)); surface->expectKey(AKEYCODE_V); } @@ -1261,19 +1258,19 @@ TEST_F(MultiDisplayTests, drop_input_for_secure_layer_on_nonsecure_display) { ui::LayerStack layerStack = ui::LayerStack::fromValue(42); createDisplay(1000, 1000, false /*isSecure*/, layerStack); std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { + surface->doTransaction([&](auto& t, auto& sc) { t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); t.setLayerStack(sc, layerStack); }); surface->showAt(100, 100); - injectTapOnDisplay(101, 101, layerStack.id); + injectTapOnDisplay(101, 101, toDisplayId(layerStack)); surface->assertNoEvent(); - surface->requestFocus(layerStack.id); + surface->requestFocus(toDisplayId(layerStack)); surface->assertFocusChange(true); - injectKeyOnDisplay(AKEYCODE_V, layerStack.id); + injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack)); surface->assertNoEvent(); } @@ -1287,20 +1284,21 @@ TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) { seteuid(AID_ROOT); std::unique_ptr surface = makeSurface(100, 100); - surface->doTransaction([&](auto &t, auto &sc) { + surface->doTransaction([&](auto& t, auto& sc) { t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure); t.setLayerStack(sc, layerStack); }); surface->showAt(100, 100); - injectTapOnDisplay(101, 101, layerStack.id); + injectTapOnDisplay(101, 101, toDisplayId(layerStack)); surface->expectTap(1, 1); - surface->requestFocus(layerStack.id); + surface->requestFocus(toDisplayId(layerStack)); surface->assertFocusChange(true); - injectKeyOnDisplay(AKEYCODE_V, layerStack.id); + injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack)); surface->expectKey(AKEYCODE_V); } -} // namespace android::test +} // namespace test +} // namespace android diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp index 5eb5d3bff0..ce22082a9f 100644 --- a/libs/gui/tests/WindowInfo_test.cpp +++ b/libs/gui/tests/WindowInfo_test.cpp @@ -64,7 +64,7 @@ TEST(WindowInfo, Parcelling) { i.ownerUid = gui::Uid{24}; i.packageName = "com.example.package"; i.inputConfig = WindowInfo::InputConfig::NOT_FOCUSABLE; - i.displayId = 34; + i.displayId = ui::LogicalDisplayId{34}; i.replaceTouchableRegionWithCrop = true; i.touchableRegionCropHandle = touchableRegionCropHandle; i.applicationInfo.name = "ApplicationFooBar"; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 61a964ece9..d27156383a 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -293,8 +292,8 @@ VerifiedMotionEvent verifiedMotionEventFromMotionEvent(const MotionEvent& event) event.getButtonState()}; } -void InputEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId, - std::array hmac) { +void InputEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, + ui::LogicalDisplayId displayId, std::array hmac) { mId = id; mDeviceId = deviceId; mSource = source; @@ -356,10 +355,11 @@ std::optional KeyEvent::getKeyCodeFromLabel(const char* label) { return InputEventLookup::getKeyCodeByLabel(label); } -void KeyEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId, - std::array hmac, int32_t action, int32_t flags, - int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount, - nsecs_t downTime, nsecs_t eventTime) { +void KeyEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, + ui::LogicalDisplayId displayId, std::array hmac, + int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, int32_t repeatCount, nsecs_t downTime, + nsecs_t eventTime) { InputEvent::initialize(id, deviceId, source, displayId, hmac); mAction = action; mFlags = flags; @@ -556,14 +556,15 @@ void PointerCoords::transform(const ui::Transform& transform) { // --- MotionEvent --- -void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId, - std::array hmac, int32_t action, int32_t actionButton, - int32_t flags, int32_t edgeFlags, int32_t metaState, - int32_t buttonState, MotionClassification classification, - const ui::Transform& transform, float xPrecision, float yPrecision, - float rawXCursorPosition, float rawYCursorPosition, - const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime, - size_t pointerCount, const PointerProperties* pointerProperties, +void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, + ui::LogicalDisplayId displayId, std::array hmac, + int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, + int32_t metaState, int32_t buttonState, + MotionClassification classification, const ui::Transform& transform, + float xPrecision, float yPrecision, float rawXCursorPosition, + float rawYCursorPosition, const ui::Transform& rawTransform, + nsecs_t downTime, nsecs_t eventTime, size_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) { InputEvent::initialize(id, deviceId, source, displayId, hmac); mAction = action; @@ -835,7 +836,7 @@ status_t MotionEvent::readFromParcel(Parcel* parcel) { mId = parcel->readInt32(); mDeviceId = parcel->readInt32(); mSource = parcel->readUint32(); - mDisplayId = parcel->readInt32(); + mDisplayId = ui::LogicalDisplayId{parcel->readInt32()}; std::vector hmac; status_t result = parcel->readByteVector(&hmac); if (result != OK || hmac.size() != 32) { @@ -903,7 +904,7 @@ status_t MotionEvent::writeToParcel(Parcel* parcel) const { parcel->writeInt32(mId); parcel->writeInt32(mDeviceId); parcel->writeUint32(mSource); - parcel->writeInt32(mDisplayId); + parcel->writeInt32(mDisplayId.val()); std::vector hmac(mHmac.begin(), mHmac.end()); parcel->writeByteVector(hmac); parcel->writeInt32(mAction); @@ -1203,7 +1204,7 @@ std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { void FocusEvent::initialize(int32_t id, bool hasFocus) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ADISPLAY_ID_NONE, INVALID_HMAC); + ui::ADISPLAY_ID_NONE, INVALID_HMAC); mHasFocus = hasFocus; } @@ -1216,7 +1217,7 @@ void FocusEvent::initialize(const FocusEvent& from) { void CaptureEvent::initialize(int32_t id, bool pointerCaptureEnabled) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ADISPLAY_ID_NONE, INVALID_HMAC); + ui::ADISPLAY_ID_NONE, INVALID_HMAC); mPointerCaptureEnabled = pointerCaptureEnabled; } @@ -1229,7 +1230,7 @@ void CaptureEvent::initialize(const CaptureEvent& from) { void DragEvent::initialize(int32_t id, float x, float y, bool isExiting) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ADISPLAY_ID_NONE, INVALID_HMAC); + ui::ADISPLAY_ID_NONE, INVALID_HMAC); mIsExiting = isExiting; mX = x; mY = y; @@ -1246,7 +1247,7 @@ void DragEvent::initialize(const DragEvent& from) { void TouchModeEvent::initialize(int32_t id, bool isInTouchMode) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ADISPLAY_ID_NONE, INVALID_HMAC); + ui::ADISPLAY_ID_NONE, INVALID_HMAC); mIsInTouchMode = isInTouchMode; } diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp index be2110e42b..abc039281a 100644 --- a/libs/input/InputConsumer.cpp +++ b/libs/input/InputConsumer.cpp @@ -81,10 +81,10 @@ bool debugResampling() { void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) { event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, - msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, - msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, - msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, - msg.body.key.eventTime); + ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac, + msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode, + msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount, + msg.body.key.downTime, msg.body.key.eventTime); } void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) { @@ -117,13 +117,14 @@ void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) { msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, 0, 0, 1}); event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, - msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, - msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, - msg.body.motion.metaState, msg.body.motion.buttonState, - msg.body.motion.classification, transform, msg.body.motion.xPrecision, - msg.body.motion.yPrecision, msg.body.motion.xCursorPosition, - msg.body.motion.yCursorPosition, displayTransform, msg.body.motion.downTime, - msg.body.motion.eventTime, pointerCount, pointerProperties, pointerCoords); + ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac, + msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags, + msg.body.motion.edgeFlags, msg.body.motion.metaState, + msg.body.motion.buttonState, msg.body.motion.classification, transform, + msg.body.motion.xPrecision, msg.body.motion.yPrecision, + msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition, + displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime, + pointerCount, pointerProperties, pointerCoords); } void addSample(MotionEvent& event, const InputMessage& msg) { diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp index 76f2b4a4f8..15d992f9f3 100644 --- a/libs/input/InputConsumerNoResampling.cpp +++ b/libs/input/InputConsumerNoResampling.cpp @@ -47,10 +47,10 @@ const bool DEBUG_TRANSPORT_CONSUMER = std::unique_ptr createKeyEvent(const InputMessage& msg) { std::unique_ptr event = std::make_unique(); event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source, - msg.body.key.displayId, msg.body.key.hmac, msg.body.key.action, - msg.body.key.flags, msg.body.key.keyCode, msg.body.key.scanCode, - msg.body.key.metaState, msg.body.key.repeatCount, msg.body.key.downTime, - msg.body.key.eventTime); + ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac, + msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode, + msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount, + msg.body.key.downTime, msg.body.key.eventTime); return event; } @@ -93,8 +93,8 @@ std::unique_ptr createMotionEvent(const InputMessage& msg) { msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw, 0, 0, 1}); event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source, - msg.body.motion.displayId, msg.body.motion.hmac, msg.body.motion.action, - msg.body.motion.actionButton, msg.body.motion.flags, + ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac, + msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags, msg.body.motion.edgeFlags, msg.body.motion.metaState, msg.body.motion.buttonState, msg.body.motion.classification, transform, msg.body.motion.xPrecision, msg.body.motion.yPrecision, diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index 222647db56..50239a1f9f 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -170,7 +169,7 @@ std::string InputDeviceIdentifier::getCanonicalName() const { // --- InputDeviceInfo --- InputDeviceInfo::InputDeviceInfo() { - initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE); + initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ui::ADISPLAY_ID_NONE); } InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) @@ -202,7 +201,8 @@ InputDeviceInfo::~InputDeviceInfo() { void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber, const InputDeviceIdentifier& identifier, const std::string& alias, - bool isExternal, bool hasMic, int32_t associatedDisplayId, + bool isExternal, bool hasMic, + ui::LogicalDisplayId associatedDisplayId, InputDeviceViewBehavior viewBehavior, bool enabled) { mId = id; mGeneration = generation; diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp index 3ca6ccc7d5..47b422857e 100644 --- a/libs/input/InputTransport.cpp +++ b/libs/input/InputTransport.cpp @@ -530,7 +530,7 @@ InputPublisher::~InputPublisher() { } status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId, - int32_t source, int32_t displayId, + int32_t source, ui::LogicalDisplayId displayId, std::array hmac, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime, @@ -558,7 +558,7 @@ status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t msg.body.key.eventId = eventId; msg.body.key.deviceId = deviceId; msg.body.key.source = source; - msg.body.key.displayId = displayId; + msg.body.key.displayId = displayId.val(); msg.body.key.hmac = std::move(hmac); msg.body.key.action = action; msg.body.key.flags = flags; @@ -572,11 +572,11 @@ status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t } status_t InputPublisher::publishMotionEvent( - uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId, - std::array hmac, int32_t action, int32_t actionButton, int32_t flags, - int32_t edgeFlags, int32_t metaState, int32_t buttonState, - MotionClassification classification, const ui::Transform& transform, float xPrecision, - float yPrecision, float xCursorPosition, float yCursorPosition, + uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, + ui::LogicalDisplayId displayId, std::array hmac, int32_t action, + int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState, + int32_t buttonState, MotionClassification classification, const ui::Transform& transform, + float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition, const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount, const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) { @@ -596,13 +596,13 @@ status_t InputPublisher::publishMotionEvent( std::string transformString; transform.dump(transformString, "transform", " "); ALOGD("channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, " - "displayId=%" PRId32 ", " + "displayId=%s, " "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, " "metaState=0x%x, buttonState=0x%x, classification=%s," "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", " "pointerCount=%" PRIu32 "\n%s", mChannel->getName().c_str(), __func__, seq, eventId, deviceId, - inputEventSourceToString(source).c_str(), displayId, + inputEventSourceToString(source).c_str(), displayId.toString().c_str(), MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags, metaState, buttonState, motionClassificationToString(classification), xPrecision, yPrecision, downTime, eventTime, pointerCount, transformString.c_str()); @@ -625,7 +625,7 @@ status_t InputPublisher::publishMotionEvent( msg.body.motion.eventId = eventId; msg.body.motion.deviceId = deviceId; msg.body.motion.source = source; - msg.body.motion.displayId = displayId; + msg.body.motion.displayId = displayId.val(); msg.body.motion.hmac = std::move(hmac); msg.body.motion.action = action; msg.body.motion.actionButton = actionButton; diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index e2feabcbbe..41909bfb2d 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -496,11 +495,11 @@ bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMeta return false; } -void KeyCharacterMap::addKey(Vector& outEvents, - int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) { +void KeyCharacterMap::addKey(Vector& outEvents, int32_t deviceId, int32_t keyCode, + int32_t metaState, bool down, nsecs_t time) { outEvents.push(); KeyEvent& event = outEvents.editTop(); - event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, + event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, INVALID_HMAC, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode, 0, metaState, 0, time, time); } diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 0df06b790e..cc2574de9a 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -21,14 +21,13 @@ #include #include #include -#include #include #include namespace android { // Default display id. -static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; +static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; @@ -229,7 +228,7 @@ TEST_F(KeyEventTest, Properties) { ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource()); // Set display id. - constexpr int32_t newDisplayId = 2; + constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2}; event.setDisplayId(newDisplayId); ASSERT_EQ(newDisplayId, event.getDisplayId()); } @@ -528,7 +527,7 @@ TEST_F(MotionEventTest, Properties) { ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource()); // Set displayId. - constexpr int32_t newDisplayId = 2; + constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2}; event.setDisplayId(newDisplayId); ASSERT_EQ(newDisplayId, event.getDisplayId()); @@ -860,7 +859,7 @@ MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); MotionEvent event; event.initialize(InputEvent::nextId(), /* deviceId */ 1, source, - /* displayId */ 0, INVALID_HMAC, action, + /* displayId */ ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, action, /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, transform, /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp index 6593497763..7ae1cd8444 100644 --- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -56,7 +55,7 @@ struct PublishMotionArgs { const int32_t eventId; const int32_t deviceId = 1; const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; - const int32_t displayId = ADISPLAY_ID_DEFAULT; + const ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; const int32_t actionButton = 0; const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; @@ -446,7 +445,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() { int32_t eventId = InputEvent::nextId(); constexpr int32_t deviceId = 1; constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD; - constexpr int32_t displayId = ADISPLAY_ID_DEFAULT; + constexpr ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; constexpr std::array hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; @@ -738,9 +737,10 @@ TEST_F(InputPublisherAndConsumerNoResamplingTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, - 0, 0, 0, MotionClassification::NONE, identityTransform, - 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, + INVALID_HMAC, 0, 0, 0, 0, 0, 0, + MotionClassification::NONE, identityTransform, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; @@ -755,9 +755,10 @@ TEST_F(InputPublisherAndConsumerNoResamplingTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, - 0, 0, 0, MotionClassification::NONE, identityTransform, - 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, + INVALID_HMAC, 0, 0, 0, 0, 0, 0, + MotionClassification::NONE, identityTransform, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; @@ -776,9 +777,10 @@ TEST_F(InputPublisherAndConsumerNoResamplingTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, - 0, 0, 0, MotionClassification::NONE, identityTransform, - 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, + INVALID_HMAC, 0, 0, 0, 0, 0, 0, + MotionClassification::NONE, identityTransform, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 332831febb..d0dbe2ad31 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include @@ -49,7 +48,7 @@ struct PublishMotionArgs { const int32_t eventId; const int32_t deviceId = 1; const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; - const int32_t displayId = ADISPLAY_ID_DEFAULT; + const ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; const int32_t actionButton = 0; const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; @@ -263,7 +262,7 @@ void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() { int32_t eventId = InputEvent::nextId(); constexpr int32_t deviceId = 1; constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD; - constexpr int32_t displayId = ADISPLAY_ID_DEFAULT; + constexpr ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; constexpr std::array hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; @@ -623,13 +622,13 @@ TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZer ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, - 0, 0, 0, MotionClassification::NONE, identityTransform, - 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, + INVALID_HMAC, 0, 0, 0, 0, 0, 0, + MotionClassification::NONE, identityTransform, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); - ASSERT_EQ(BAD_VALUE, status) - << "publisher publishMotionEvent should return BAD_VALUE"; + ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; } TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) { @@ -640,17 +639,17 @@ TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessTha ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, - 0, 0, 0, MotionClassification::NONE, identityTransform, - 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, + INVALID_HMAC, 0, 0, 0, 0, 0, 0, + MotionClassification::NONE, identityTransform, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); - ASSERT_EQ(BAD_VALUE, status) - << "publisher publishMotionEvent should return BAD_VALUE"; + ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; } TEST_F(InputPublisherAndConsumerTest, - PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) { + PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) { status_t status; const size_t pointerCount = MAX_POINTERS + 1; PointerProperties pointerProperties[pointerCount]; @@ -662,13 +661,13 @@ TEST_F(InputPublisherAndConsumerTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0, - 0, 0, 0, MotionClassification::NONE, identityTransform, - 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, + INVALID_HMAC, 0, 0, 0, 0, 0, 0, + MotionClassification::NONE, identityTransform, 0, 0, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); - ASSERT_EQ(BAD_VALUE, status) - << "publisher publishMotionEvent should return BAD_VALUE"; + ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; } TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) { diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index b8f1caa068..1c9f0c7399 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -59,8 +58,8 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, } ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {0}, - action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, + event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ui::ADISPLAY_ID_DEFAULT, + {0}, action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, /*xPrecision=*/0.1, /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540, diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 6e23d4e910..1694cadfbe 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -84,7 +84,8 @@ status_t TouchResamplingTest::publishSimpleMotionEventWithCoords( ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)"; } return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1, - AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, INVALID_HMAC, + AINPUT_SOURCE_TOUCHSCREEN, + /*displayId=*/ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, /*xPrecision=*/0, /*yPrecision=*/0, diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index f9ca28083d..b4c4e1286d 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include using std::literals::chrono_literals::operator""ms; @@ -34,7 +33,7 @@ using android::base::StringPrintf; namespace android { -constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; // default display id +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; // default display id constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually defined tests @@ -156,7 +155,7 @@ static std::vector createAxisScrollMotionEventStream( MotionEvent event; ui::Transform identityTransform; event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER, - ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL, + ui::ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp index 277d74dd1c..40cfaaeb05 100644 --- a/libs/input/tests/VerifiedInputEvent_test.cpp +++ b/libs/input/tests/VerifiedInputEvent_test.cpp @@ -16,7 +16,6 @@ #include #include -#include #include namespace android { @@ -24,7 +23,7 @@ namespace android { static KeyEvent getKeyEventWithFlags(int32_t flags) { KeyEvent event; event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD, - ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags, + ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags, AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1, /*downTime=*/1000, /*eventTime=*/2000); return event; @@ -44,10 +43,11 @@ static MotionEvent getMotionEventWithFlags(int32_t flags) { ui::Transform transform; transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1}); ui::Transform identity; - event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT, - INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, flags, - AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, - MotionClassification::NONE, transform, /*xPrecision=*/0.1, /*yPrecision=*/0.2, + event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, + ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, + /*actionButton=*/0, flags, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, + /*buttonState=*/0, MotionClassification::NONE, transform, /*xPrecision=*/0.1, + /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540, identity, /*downTime=*/100, /*eventTime=*/200, pointerCount, pointerProperties, pointerCoords); return event; diff --git a/libs/ui/include/ui/LogicalDisplayId.h b/libs/ui/include/ui/LogicalDisplayId.h new file mode 100644 index 0000000000..3e504e7101 --- /dev/null +++ b/libs/ui/include/ui/LogicalDisplayId.h @@ -0,0 +1,56 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace android::ui { + +// Type-safe wrapper for a logical display id. +struct LogicalDisplayId : ftl::Constructible, + ftl::Equatable, + ftl::Orderable { + using Constructible::Constructible; + + constexpr auto val() const { return ftl::to_underlying(*this); } + + constexpr bool isValid() const { return val() >= 0; } + + std::string toString() const { return std::to_string(val()); } +}; + +constexpr LogicalDisplayId ADISPLAY_ID_NONE{-1}; +constexpr LogicalDisplayId ADISPLAY_ID_DEFAULT{0}; + +inline std::ostream& operator<<(std::ostream& stream, LogicalDisplayId displayId) { + return stream << displayId.val(); +} + +} // namespace android::ui + +namespace std { +template <> +struct hash { + size_t operator()(const android::ui::LogicalDisplayId& displayId) const { + return hash()(displayId.val()); + } +}; +} // namespace std \ No newline at end of file diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 417c1f333b..e376734b86 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -314,7 +314,7 @@ common::MotionEvent notifyMotionArgsToHalMotionEvent(const NotifyMotionArgs& arg common::MotionEvent event; event.deviceId = args.deviceId; event.source = getSource(args.source); - event.displayId = args.displayId; + event.displayId = args.displayId.val(); event.downTime = args.downTime; event.eventTime = args.eventTime; event.deviceTimestamp = 0; diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp index 1ada5e5678..8e73ce5d94 100644 --- a/services/inputflinger/InputFilter.cpp +++ b/services/inputflinger/InputFilter.cpp @@ -32,7 +32,7 @@ AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) { event.eventTime = args.eventTime; event.deviceId = args.deviceId; event.source = static_cast(args.source); - event.displayId = args.displayId; + event.displayId = args.displayId.val(); event.policyFlags = args.policyFlags; event.action = static_cast(args.action); event.flags = args.flags; diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp index a9bdbec105..5fbdc84c4b 100644 --- a/services/inputflinger/InputFilterCallbacks.cpp +++ b/services/inputflinger/InputFilterCallbacks.cpp @@ -30,9 +30,9 @@ using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent; NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) { return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId, - static_cast(event.source), event.displayId, event.policyFlags, - static_cast(event.action), event.flags, event.keyCode, - event.scanCode, event.metaState, event.downTime); + static_cast(event.source), ui::LogicalDisplayId{event.displayId}, + event.policyFlags, static_cast(event.action), event.flags, + event.keyCode, event.scanCode, event.metaState, event.downTime); } namespace { diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp index 4ec5b898b1..29b487f1d2 100644 --- a/services/inputflinger/InputReaderBase.cpp +++ b/services/inputflinger/InputReaderBase.cpp @@ -68,7 +68,7 @@ std::optional InputReaderConfiguration::getDisplayViewportByTyp if (currentViewport.type == type) { if (!result || (type == ViewportType::INTERNAL && - currentViewport.displayId == ADISPLAY_ID_DEFAULT)) { + currentViewport.displayId == ui::ADISPLAY_ID_DEFAULT)) { result = std::make_optional(currentViewport); } count++; @@ -93,7 +93,7 @@ std::optional InputReaderConfiguration::getDisplayViewportByPor } std::optional InputReaderConfiguration::getDisplayViewportById( - int32_t displayId) const { + ui::LogicalDisplayId displayId) const { for (const DisplayViewport& currentViewport : mDisplays) { if (currentViewport.displayId == displayId) { return std::make_optional(currentViewport); diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp index de836e93a4..19a4f26a1e 100644 --- a/services/inputflinger/NotifyArgs.cpp +++ b/services/inputflinger/NotifyArgs.cpp @@ -43,7 +43,7 @@ NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs // --- NotifyKeyArgs --- NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, - uint32_t source, int32_t displayId, uint32_t policyFlags, + uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) : id(id), @@ -64,7 +64,7 @@ NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, in NotifyMotionArgs::NotifyMotionArgs( int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, uint32_t source, - int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton, + ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, float xPrecision, float yPrecision, diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index a3d0c2b488..d6c6f938d4 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -67,8 +67,9 @@ bool isMouseOrTouchpad(uint32_t sources) { !isFromSource(sources, AINPUT_SOURCE_STYLUS)); } -inline void notifyPointerDisplayChange(std::optional> change, - PointerChoreographerPolicyInterface& policy) { +inline void notifyPointerDisplayChange( + std::optional> change, + PointerChoreographerPolicyInterface& policy) { if (!change) { return; } @@ -100,8 +101,8 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, }), mNextListener(listener), mPolicy(policy), - mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT), - mNotifiedPointerDisplayId(ADISPLAY_ID_NONE), + mDefaultMouseDisplayId(ui::ADISPLAY_ID_DEFAULT), + mNotifiedPointerDisplayId(ui::ADISPLAY_ID_NONE), mShowTouchesEnabled(false), mStylusPointerIconEnabled(false) {} @@ -235,7 +236,7 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo } void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) { - if (args.displayId == ADISPLAY_ID_NONE) { + if (args.displayId == ui::ADISPLAY_ID_NONE) { return; } @@ -272,7 +273,7 @@ void PointerChoreographer::processDrawingTabletEventLocked(const android::Notify * For touch events, we do not need to populate the cursor position. */ void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) { - if (args.displayId == ADISPLAY_ID_NONE) { + if (!args.displayId.isValid()) { return; } @@ -315,7 +316,7 @@ void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMo } void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) { - if (args.displayId == ADISPLAY_ID_NONE) { + if (!args.displayId.isValid()) { return; } @@ -423,7 +424,7 @@ void PointerChoreographer::dump(std::string& dump) { dump += INDENT "MousePointerControllers:\n"; for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) { std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT); - dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump; + dump += INDENT + displayId.toString() + " : " + pointerControllerDump; } dump += INDENT "TouchPointerControllers:\n"; for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) { @@ -443,7 +444,8 @@ void PointerChoreographer::dump(std::string& dump) { dump += "\n"; } -const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const { +const DisplayViewport* PointerChoreographer::findViewportByIdLocked( + ui::LogicalDisplayId displayId) const { for (auto& viewport : mViewports) { if (viewport.displayId == displayId) { return &viewport; @@ -452,13 +454,14 @@ const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t disp return nullptr; } -int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const { - return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId; +ui::LogicalDisplayId PointerChoreographer::getTargetMouseDisplayLocked( + ui::LogicalDisplayId associatedDisplayId) const { + return associatedDisplayId.isValid() ? associatedDisplayId : mDefaultMouseDisplayId; } -std::pair PointerChoreographer::ensureMouseControllerLocked( - int32_t associatedDisplayId) { - const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId); +std::pair +PointerChoreographer::ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) { + const ui::LogicalDisplayId displayId = getTargetMouseDisplayLocked(associatedDisplayId); auto it = mMousePointersByDisplay.find(displayId); if (it == mMousePointersByDisplay.end()) { @@ -476,12 +479,12 @@ InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) return it != mInputDeviceInfos.end() ? &(*it) : nullptr; } -bool PointerChoreographer::canUnfadeOnDisplay(int32_t displayId) { +bool PointerChoreographer::canUnfadeOnDisplay(ui::LogicalDisplayId displayId) { return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end(); } PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() { - std::set mouseDisplaysToKeep; + std::set mouseDisplaysToKeep; std::set touchDevicesToKeep; std::set stylusDevicesToKeep; std::set drawingTabletDevicesToKeep; @@ -498,7 +501,8 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo const bool isKnownMouse = mMouseDevices.count(info.getId()) != 0; if (isMouseOrTouchpad(sources) || isKnownMouse) { - const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId()); + const ui::LogicalDisplayId displayId = + getTargetMouseDisplayLocked(info.getAssociatedDisplayId()); mouseDisplaysToKeep.insert(displayId); // For mice, show the cursor immediately when the device is first connected or // when it moves to a new display. @@ -513,15 +517,15 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo } } if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled && - info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { + info.getAssociatedDisplayId().isValid()) { touchDevicesToKeep.insert(info.getId()); } if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled && - info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { + info.getAssociatedDisplayId().isValid()) { stylusDevicesToKeep.insert(info.getId()); } if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) && - info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) { + info.getAssociatedDisplayId().isValid()) { drawingTabletDevicesToKeep.insert(info.getId()); } } @@ -553,7 +557,7 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo PointerChoreographer::PointerDisplayChange PointerChoreographer::calculatePointerDisplayChangeToNotify() { - int32_t displayIdToNotify = ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayIdToNotify = ui::ADISPLAY_ID_NONE; FloatPoint cursorPosition = {0, 0}; if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId); it != mMousePointersByDisplay.end()) { @@ -571,7 +575,7 @@ PointerChoreographer::calculatePointerDisplayChangeToNotify() { return {{displayIdToNotify, cursorPosition}}; } -void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) { +void PointerChoreographer::setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) { PointerDisplayChange pointerDisplayChange; { // acquire lock @@ -590,7 +594,7 @@ void PointerChoreographer::setDisplayViewports(const std::vectorsecond->setDisplayViewport(viewport); @@ -616,18 +620,18 @@ void PointerChoreographer::setDisplayViewports(const std::vector PointerChoreographer::getViewportForPointerDevice( - int32_t associatedDisplayId) { + ui::LogicalDisplayId associatedDisplayId) { std::scoped_lock _l(mLock); - const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId); + const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId); if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) { return *viewport; } return std::nullopt; } -FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) { +FloatPoint PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) { std::scoped_lock _l(mLock); - const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId); + const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(displayId); if (auto it = mMousePointersByDisplay.find(resolvedDisplayId); it != mMousePointersByDisplay.end()) { return it->second->getPosition(); @@ -666,8 +670,8 @@ void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) { } bool PointerChoreographer::setPointerIcon( - std::variant, PointerIconStyle> icon, int32_t displayId, - DeviceId deviceId) { + std::variant, PointerIconStyle> icon, + ui::LogicalDisplayId displayId, DeviceId deviceId) { std::scoped_lock _l(mLock); if (deviceId < 0) { LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon."; @@ -715,8 +719,8 @@ void PointerChoreographer::onWindowInfosChangedLocked( const std::vector& windowInfos) { // Mark all spot controllers secure on displays containing secure windows and // remove secure flag from others if required - std::unordered_set privacySensitiveDisplays; - std::unordered_set allDisplayIds; + std::unordered_set privacySensitiveDisplays; + std::unordered_set allDisplayIds; for (const auto& windowInfo : windowInfos) { allDisplayIds.insert(windowInfo.displayId); if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) && @@ -727,7 +731,7 @@ void PointerChoreographer::onWindowInfosChangedLocked( for (auto& it : mTouchPointersByDevice) { auto& pc = it.second; - for (int32_t displayId : allDisplayIds) { + for (ui::LogicalDisplayId displayId : allDisplayIds) { pc->setSkipScreenshot(displayId, privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()); @@ -736,7 +740,7 @@ void PointerChoreographer::onWindowInfosChangedLocked( // TODO (b/325252005): update skip screenshot flag for other types of pointer controllers } -void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) { +void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) { std::scoped_lock lock(mLock); if (visible) { mDisplaysWithPointersHidden.erase(displayId); @@ -759,7 +763,7 @@ void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visi } PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor( - int32_t displayId) { + ui::LogicalDisplayId displayId) { std::function()> ctor = [this, displayId]() REQUIRES(mLock) { auto pc = mPolicy.createPointerController( @@ -773,7 +777,7 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseContro } PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor( - int32_t displayId) { + ui::LogicalDisplayId displayId) { std::function()> ctor = [this, displayId]() REQUIRES(mLock) { auto pc = mPolicy.createPointerController( diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index b29d9cdb7c..fda5f5231e 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -54,11 +54,11 @@ public: * Set the display that pointers, like the mouse cursor and drawing tablets, * should be drawn on. */ - virtual void setDefaultMouseDisplayId(int32_t displayId) = 0; + virtual void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) = 0; virtual void setDisplayViewports(const std::vector& viewports) = 0; virtual std::optional getViewportForPointerDevice( - int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0; - virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0; + ui::LogicalDisplayId associatedDisplayId = ui::ADISPLAY_ID_NONE) = 0; + virtual FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0; virtual void setShowTouchesEnabled(bool enabled) = 0; virtual void setStylusPointerIconEnabled(bool enabled) = 0; /** @@ -67,12 +67,12 @@ public: * Returns true if the icon was changed successfully, false otherwise. */ virtual bool setPointerIcon(std::variant, PointerIconStyle> icon, - int32_t displayId, DeviceId deviceId) = 0; + ui::LogicalDisplayId displayId, DeviceId deviceId) = 0; /** * Set whether pointer icons for mice, touchpads, and styluses should be visible on the * given display. */ - virtual void setPointerIconVisibility(int32_t displayId, bool visible) = 0; + virtual void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) = 0; /** * This method may be called on any thread (usually by the input manager on a binder thread). @@ -86,16 +86,16 @@ public: PointerChoreographerPolicyInterface&); ~PointerChoreographer() override; - void setDefaultMouseDisplayId(int32_t displayId) override; + void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) override; void setDisplayViewports(const std::vector& viewports) override; std::optional getViewportForPointerDevice( - int32_t associatedDisplayId) override; - FloatPoint getMouseCursorPosition(int32_t displayId) override; + ui::LogicalDisplayId associatedDisplayId) override; + FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) override; void setShowTouchesEnabled(bool enabled) override; void setStylusPointerIconEnabled(bool enabled) override; bool setPointerIcon(std::variant, PointerIconStyle> icon, - int32_t displayId, DeviceId deviceId) override; - void setPointerIconVisibility(int32_t displayId, bool visible) override; + ui::LogicalDisplayId displayId, DeviceId deviceId) override; + void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override; void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; @@ -113,16 +113,18 @@ public: void dump(std::string& dump) override; private: - using PointerDisplayChange = - std::optional>; + using PointerDisplayChange = std::optional< + std::tuple>; [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock); [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock); - const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock); - int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock); - std::pair ensureMouseControllerLocked( - int32_t associatedDisplayId) REQUIRES(mLock); + const DisplayViewport* findViewportByIdLocked(ui::LogicalDisplayId displayId) const + REQUIRES(mLock); + ui::LogicalDisplayId getTargetMouseDisplayLocked(ui::LogicalDisplayId associatedDisplayId) const + REQUIRES(mLock); + std::pair + ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) REQUIRES(mLock); InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock); - bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock); + bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock); NotifyMotionArgs processMotion(const NotifyMotionArgs& args); NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); @@ -151,16 +153,18 @@ private: using ControllerConstructor = ConstructorDelegate()>>; ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock); - ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock); - ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock); + ControllerConstructor getMouseControllerConstructor(ui::LogicalDisplayId displayId) + REQUIRES(mLock); + ControllerConstructor getStylusControllerConstructor(ui::LogicalDisplayId displayId) + REQUIRES(mLock); std::mutex mLock; InputListenerInterface& mNextListener; PointerChoreographerPolicyInterface& mPolicy; - std::map> mMousePointersByDisplay - GUARDED_BY(mLock); + std::map> + mMousePointersByDisplay GUARDED_BY(mLock); std::map> mTouchPointersByDevice GUARDED_BY(mLock); std::map> mStylusPointersByDevice @@ -168,14 +172,14 @@ private: std::map> mDrawingTabletPointersByDevice GUARDED_BY(mLock); - int32_t mDefaultMouseDisplayId GUARDED_BY(mLock); - int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock); + ui::LogicalDisplayId mDefaultMouseDisplayId GUARDED_BY(mLock); + ui::LogicalDisplayId mNotifiedPointerDisplayId GUARDED_BY(mLock); std::vector mInputDeviceInfos GUARDED_BY(mLock); std::set mMouseDevices GUARDED_BY(mLock); std::vector mViewports GUARDED_BY(mLock); bool mShowTouchesEnabled GUARDED_BY(mLock); bool mStylusPointerIconEnabled GUARDED_BY(mLock); - std::set mDisplaysWithPointersHidden; + std::set mDisplaysWithPointersHidden; }; } // namespace android diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 5f955908c5..66e4a297dd 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -18,7 +18,6 @@ #include #include -#include #include "../dispatcher/InputDispatcher.h" #include "../tests/FakeApplicationHandle.h" #include "../tests/FakeInputDispatcherPolicy.h" @@ -38,7 +37,7 @@ namespace { constexpr DeviceId DEVICE_ID = 1; // An arbitrary display id -constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s; @@ -63,7 +62,7 @@ static MotionEvent generateMotionEvent() { ui::Transform identityTransform; MotionEvent event; event.initialize(IInputConstants::INVALID_INPUT_EVENT_ID, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, + ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, identityTransform, /* xPrecision */ 0, @@ -89,7 +88,7 @@ static NotifyMotionArgs generateMotionArgs() { const nsecs_t currentTime = now(); // Define a valid motion event. NotifyMotionArgs args(IInputConstants::INVALID_INPUT_EVENT_ID, currentTime, currentTime, - DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ui::ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN, /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h index 9c73f03dc8..4a0889f596 100644 --- a/services/inputflinger/dispatcher/CancelationOptions.h +++ b/services/inputflinger/dispatcher/CancelationOptions.h @@ -48,7 +48,7 @@ struct CancelationOptions { std::optional deviceId = std::nullopt; // The specific display id of events to cancel, or nullopt to cancel events on any display. - std::optional displayId = std::nullopt; + std::optional displayId = std::nullopt; // The specific pointers to cancel, or nullopt to cancel all pointer events std::optional> pointerIds = std::nullopt; diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp index c8bc87f035..ad9cec1791 100644 --- a/services/inputflinger/dispatcher/Entry.cpp +++ b/services/inputflinger/dispatcher/Entry.cpp @@ -132,9 +132,9 @@ std::string DragEntry::getDescription() const { // --- KeyEntry --- KeyEntry::KeyEntry(int32_t id, std::shared_ptr injectionState, nsecs_t eventTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, - int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, - int32_t metaState, int32_t repeatCount, nsecs_t downTime) + int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, + int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime) : EventEntry(id, Type::KEY, eventTime, policyFlags), deviceId(deviceId), source(source), @@ -156,13 +156,14 @@ std::string KeyEntry::getDescription() const { if (!IS_DEBUGGABLE_BUILD) { return "KeyEvent"; } - return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32 - ", action=%s, " + return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%s, " + "action=%s, " "flags=0x%08x, keyCode=%s(%d), scanCode=%d, metaState=0x%08x, " "repeatCount=%d), policyFlags=0x%08x", - deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId, - KeyEvent::actionToString(action), flags, KeyEvent::getLabel(keyCode), - keyCode, scanCode, metaState, repeatCount, policyFlags); + deviceId, eventTime, inputEventSourceToString(source).c_str(), + displayId.toString().c_str(), KeyEvent::actionToString(action), flags, + KeyEvent::getLabel(keyCode), keyCode, scanCode, metaState, repeatCount, + policyFlags); } std::ostream& operator<<(std::ostream& out, const KeyEntry& keyEntry) { @@ -172,7 +173,8 @@ std::ostream& operator<<(std::ostream& out, const KeyEntry& keyEntry) { // --- TouchModeEntry --- -TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId) +TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, + ui::LogicalDisplayId displayId) : EventEntry(id, Type::TOUCH_MODE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER), inTouchMode(inTouchMode), displayId(displayId) {} @@ -184,12 +186,13 @@ std::string TouchModeEntry::getDescription() const { // --- MotionEntry --- MotionEntry::MotionEntry(int32_t id, std::shared_ptr injectionState, - nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId, - uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags, - int32_t metaState, int32_t buttonState, - MotionClassification classification, int32_t edgeFlags, float xPrecision, - float yPrecision, float xCursorPosition, float yCursorPosition, - nsecs_t downTime, const std::vector& pointerProperties, + nsecs_t eventTime, int32_t deviceId, uint32_t source, + ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action, + int32_t actionButton, int32_t flags, int32_t metaState, + int32_t buttonState, MotionClassification classification, + int32_t edgeFlags, float xPrecision, float yPrecision, + float xCursorPosition, float yCursorPosition, nsecs_t downTime, + const std::vector& pointerProperties, const std::vector& pointerCoords) : EventEntry(id, Type::MOTION, eventTime, policyFlags), deviceId(deviceId), @@ -218,15 +221,16 @@ std::string MotionEntry::getDescription() const { } std::string msg; msg += StringPrintf("MotionEvent(deviceId=%d, eventTime=%" PRIu64 - ", source=%s, displayId=%" PRId32 - ", action=%s, actionButton=0x%08x, flags=0x%08x, metaState=0x%08x, " + ", source=%s, displayId=%s, action=%s, actionButton=0x%08x, flags=0x%08x," + " metaState=0x%08x, " "buttonState=0x%08x, " "classification=%s, edgeFlags=0x%08x, xPrecision=%.1f, yPrecision=%.1f, " "xCursorPosition=%0.1f, yCursorPosition=%0.1f, pointers=[", - deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId, - MotionEvent::actionToString(action).c_str(), actionButton, flags, metaState, - buttonState, motionClassificationToString(classification), edgeFlags, - xPrecision, yPrecision, xCursorPosition, yCursorPosition); + deviceId, eventTime, inputEventSourceToString(source).c_str(), + displayId.toString().c_str(), MotionEvent::actionToString(action).c_str(), + actionButton, flags, metaState, buttonState, + motionClassificationToString(classification), edgeFlags, xPrecision, + yPrecision, xCursorPosition, yCursorPosition); for (uint32_t i = 0; i < getPointerCount(); i++) { if (i) { diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h index 06d5c7dd4b..f2f31d88ef 100644 --- a/services/inputflinger/dispatcher/Entry.h +++ b/services/inputflinger/dispatcher/Entry.h @@ -120,7 +120,7 @@ struct DragEntry : EventEntry { struct KeyEntry : EventEntry { int32_t deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId; int32_t action; int32_t keyCode; int32_t scanCode; @@ -144,9 +144,9 @@ struct KeyEntry : EventEntry { mutable int32_t repeatCount; KeyEntry(int32_t id, std::shared_ptr injectionState, nsecs_t eventTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, - int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, - int32_t repeatCount, nsecs_t downTime); + int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, + uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, int32_t repeatCount, nsecs_t downTime); std::string getDescription() const override; }; @@ -155,7 +155,7 @@ std::ostream& operator<<(std::ostream& out, const KeyEntry& motionEntry); struct MotionEntry : EventEntry { int32_t deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId; int32_t action; int32_t actionButton; int32_t flags; @@ -175,11 +175,12 @@ struct MotionEntry : EventEntry { size_t getPointerCount() const { return pointerProperties.size(); } MotionEntry(int32_t id, std::shared_ptr injectionState, nsecs_t eventTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, - int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, - int32_t buttonState, MotionClassification classification, int32_t edgeFlags, - float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition, - nsecs_t downTime, const std::vector& pointerProperties, + int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, + uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags, + int32_t metaState, int32_t buttonState, MotionClassification classification, + int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition, + float yCursorPosition, nsecs_t downTime, + const std::vector& pointerProperties, const std::vector& pointerCoords); std::string getDescription() const override; }; @@ -205,9 +206,9 @@ struct SensorEntry : EventEntry { struct TouchModeEntry : EventEntry { bool inTouchMode; - int32_t displayId; + ui::LogicalDisplayId displayId; - TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int32_t displayId); + TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, ui::LogicalDisplayId displayId); std::string getDescription() const override; }; diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp index 0e4e79e88e..b374fad4bc 100644 --- a/services/inputflinger/dispatcher/FocusResolver.cpp +++ b/services/inputflinger/dispatcher/FocusResolver.cpp @@ -41,12 +41,12 @@ struct SpHash { size_t operator()(const sp& k) const { return std::hash()(k.get()); } }; -sp FocusResolver::getFocusedWindowToken(int32_t displayId) const { +sp FocusResolver::getFocusedWindowToken(ui::LogicalDisplayId displayId) const { auto it = mFocusedWindowTokenByDisplay.find(displayId); return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr; } -std::optional FocusResolver::getFocusRequest(int32_t displayId) { +std::optional FocusResolver::getFocusRequest(ui::LogicalDisplayId displayId) { auto it = mFocusRequestByDisplay.find(displayId); return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt; } @@ -58,7 +58,7 @@ std::optional FocusResolver::getFocusRequest(int32_t displayId) { * we will check if the previous focus request is eligible to receive focus. */ std::optional FocusResolver::setInputWindows( - int32_t displayId, const std::vector>& windows) { + ui::LogicalDisplayId displayId, const std::vector>& windows) { std::string removeFocusReason; const std::optional request = getFocusRequest(displayId); @@ -94,12 +94,11 @@ std::optional FocusResolver::setInputWindows( std::optional FocusResolver::setFocusedWindow( const FocusRequest& request, const std::vector>& windows) { - const int32_t displayId = request.displayId; + const ui::LogicalDisplayId displayId = ui::LogicalDisplayId{request.displayId}; const sp currentFocus = getFocusedWindowToken(displayId); if (currentFocus == request.token) { - ALOGD_IF(DEBUG_FOCUS, - "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused", - request.windowName.c_str(), displayId); + ALOGD_IF(DEBUG_FOCUS, "setFocusedWindow %s on display %s ignored, reason: already focused", + request.windowName.c_str(), displayId.toString().c_str()); return std::nullopt; } @@ -193,7 +192,7 @@ FocusResolver::Focusability FocusResolver::isTokenFocusable( } std::optional FocusResolver::updateFocusedWindow( - int32_t displayId, const std::string& reason, const sp& newFocus, + ui::LogicalDisplayId displayId, const std::string& reason, const sp& newFocus, const std::string& tokenName) { sp oldFocus = getFocusedWindowToken(displayId); if (newFocus == oldFocus) { @@ -216,8 +215,8 @@ std::string FocusResolver::dumpFocusedWindows() const { std::string dump; dump += INDENT "FocusedWindows:\n"; for (const auto& [displayId, namedToken] : mFocusedWindowTokenByDisplay) { - dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId, - namedToken.first.c_str()); + dump += base::StringPrintf(INDENT2 "displayId=%s, name='%s'\n", + displayId.toString().c_str(), namedToken.first.c_str()); } return dump; } @@ -233,13 +232,14 @@ std::string FocusResolver::dump() const { auto it = mLastFocusResultByDisplay.find(displayId); std::string result = it != mLastFocusResultByDisplay.end() ? ftl::enum_string(it->second) : ""; - dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n", - displayId, request.windowName.c_str(), result.c_str()); + dump += base::StringPrintf(INDENT2 "displayId=%s, name='%s' result='%s'\n", + displayId.toString().c_str(), request.windowName.c_str(), + result.c_str()); } return dump; } -void FocusResolver::displayRemoved(int32_t displayId) { +void FocusResolver::displayRemoved(ui::LogicalDisplayId displayId) { mFocusRequestByDisplay.erase(displayId); mLastFocusResultByDisplay.erase(displayId); } diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h index 5bb157b7c6..2910ba44c8 100644 --- a/services/inputflinger/dispatcher/FocusResolver.h +++ b/services/inputflinger/dispatcher/FocusResolver.h @@ -49,22 +49,23 @@ namespace android::inputdispatcher { class FocusResolver { public: // Returns the focused window token on the specified display. - sp getFocusedWindowToken(int32_t displayId) const; + sp getFocusedWindowToken(ui::LogicalDisplayId displayId) const; struct FocusChanges { sp oldFocus; sp newFocus; - int32_t displayId; + ui::LogicalDisplayId displayId; std::string reason; }; std::optional setInputWindows( - int32_t displayId, const std::vector>& windows); + ui::LogicalDisplayId displayId, + const std::vector>& windows); std::optional setFocusedWindow( const android::gui::FocusRequest& request, const std::vector>& windows); // Display has been removed from the system, clean up old references. - void displayRemoved(int32_t displayId); + void displayRemoved(ui::LogicalDisplayId displayId); // exposed for debugging bool hasFocusedWindowTokens() const { return !mFocusedWindowTokenByDisplay.empty(); } @@ -105,20 +106,23 @@ private: // the same token. Focus is tracked by the token per display and the events are dispatched // to the channel associated by this token. typedef std::pair> NamedToken; - std::unordered_map mFocusedWindowTokenByDisplay; + std::unordered_map + mFocusedWindowTokenByDisplay; // This map will store the focus request per display. When the input window handles are updated, // the current request will be checked to see if it can be processed at that time. - std::unordered_map mFocusRequestByDisplay; + std::unordered_map + mFocusRequestByDisplay; // Last reason for not granting a focus request. This is used to add more debug information // in the event logs. - std::unordered_map mLastFocusResultByDisplay; + std::unordered_map + mLastFocusResultByDisplay; std::optional updateFocusedWindow( - int32_t displayId, const std::string& reason, const sp& token, + ui::LogicalDisplayId displayId, const std::string& reason, const sp& token, const std::string& tokenName = ""); - std::optional getFocusRequest(int32_t displayId); + std::optional getFocusRequest(ui::LogicalDisplayId displayId); }; } // namespace android::inputdispatcher diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 79b8560590..c4bfa624f5 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -571,8 +571,8 @@ bool isUserActivityEvent(const EventEntry& eventEntry) { } // Returns true if the given window can accept pointer events at the given display location. -bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float x, float y, - bool isStylus, const ui::Transform& displayTransform) { +bool windowAcceptsTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, + float y, bool isStylus, const ui::Transform& displayTransform) { const auto inputConfig = windowInfo.inputConfig; if (windowInfo.displayId != displayId || inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) { @@ -600,8 +600,8 @@ bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float // Returns true if the given window's frame can occlude pointer events at the given display // location. -bool windowOccludesTouchAt(const WindowInfo& windowInfo, int displayId, float x, float y, - const ui::Transform& displayTransform) { +bool windowOccludesTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x, + float y, const ui::Transform& displayTransform) { if (windowInfo.displayId != displayId) { return false; } @@ -815,9 +815,9 @@ bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) { /** * Return true if stylus is currently down anywhere on the specified display, and false otherwise. */ -bool isStylusActiveInDisplay( - int32_t displayId, - const std::unordered_map& touchStatesByDisplay) { +bool isStylusActiveInDisplay(ui::LogicalDisplayId displayId, + const std::unordered_map& touchStatesByDisplay) { const auto it = touchStatesByDisplay.find(displayId); if (it == touchStatesByDisplay.end()) { return false; @@ -918,8 +918,9 @@ InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, mDispatchFrozen(false), mInputFilterEnabled(false), mMaximumObscuringOpacityForTouch(1.0f), - mFocusedDisplayId(ADISPLAY_ID_DEFAULT), + mFocusedDisplayId(ui::ADISPLAY_ID_DEFAULT), mWindowTokenWithPointerCapture(nullptr), + mAwaitedApplicationDisplayId(ui::ADISPLAY_ID_NONE), mLatencyAggregator(), mLatencyTracker(&mLatencyAggregator) { mLooper = sp::make(false); @@ -1286,7 +1287,7 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt // If the application takes too long to catch up then we drop all events preceding // the touch into the other window. if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) { - const int32_t displayId = motionEntry.displayId; + const ui::LogicalDisplayId displayId = motionEntry.displayId; const auto [x, y] = resolveTouchedPosition(motionEntry); const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0); @@ -1411,8 +1412,8 @@ void InputDispatcher::addRecentEventLocked(std::shared_ptr ent } } -sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y, - bool isStylus, +sp InputDispatcher::findTouchedWindowAtLocked(ui::LogicalDisplayId displayId, + float x, float y, bool isStylus, bool ignoreDragWindow) const { // Traverse windows from front to back to find touched window. const auto& windowHandles = getWindowHandlesLocked(displayId); @@ -1431,7 +1432,8 @@ sp InputDispatcher::findTouchedWindowAtLocked(int32_t displayI } std::vector InputDispatcher::findOutsideTargetsLocked( - int32_t displayId, const sp& touchedWindow, int32_t pointerId) const { + ui::LogicalDisplayId displayId, const sp& touchedWindow, + int32_t pointerId) const { if (touchedWindow == nullptr) { return {}; } @@ -1458,7 +1460,7 @@ std::vector InputDispatcher::findOutsideTargetsLocked( } std::vector> InputDispatcher::findTouchedSpyWindowsAtLocked( - int32_t displayId, float x, float y, bool isStylus) const { + ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const { // Traverse windows from front to back and gather the touched spy windows. std::vector> spyWindows; const auto& windowHandles = getWindowHandlesLocked(displayId); @@ -1951,12 +1953,12 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr(entry); @@ -2220,10 +2221,10 @@ int32_t InputDispatcher::getTargetDisplayId(const EventEntry& entry) { case EventEntry::Type::SENSOR: case EventEntry::Type::DRAG: { ALOGE("%s events do not have a target display", ftl::enum_string(entry.type).c_str()); - return ADISPLAY_ID_NONE; + return ui::ADISPLAY_ID_NONE; } } - return displayId == ADISPLAY_ID_NONE ? mFocusedDisplayId : displayId; + return displayId == ui::ADISPLAY_ID_NONE ? mFocusedDisplayId : displayId; } bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime, @@ -2262,7 +2263,7 @@ sp InputDispatcher::findFocusedWindowTargetLocked( InputEventInjectionResult& outInjectionResult) { outInjectionResult = InputEventInjectionResult::FAILED; // Default result - int32_t displayId = getTargetDisplayId(entry); + ui::LogicalDisplayId displayId = getTargetDisplayId(entry); sp focusedWindowHandle = getFocusedWindowHandleLocked(displayId); std::shared_ptr focusedApplicationHandle = getValueByKey(mFocusedApplicationHandlesByDisplay, displayId); @@ -2271,8 +2272,8 @@ sp InputDispatcher::findFocusedWindowTargetLocked( // then drop the event. if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) { ALOGI("Dropping %s event because there is no focused window or focused application in " - "display %" PRId32 ".", - ftl::enum_string(entry.type).c_str(), displayId); + "display %s.", + ftl::enum_string(entry.type).c_str(), displayId.toString().c_str()); return nullptr; } @@ -2380,7 +2381,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( std::vector targets; // For security reasons, we defer updating the touch state until we are sure that // event injection will be allowed. - const int32_t displayId = entry.displayId; + const ui::LogicalDisplayId displayId = entry.displayId; const int32_t action = entry.action; const int32_t maskedAction = MotionEvent::getActionMasked(action); @@ -2450,7 +2451,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( } // Handle the case where we did not find a window. if (newTouchedWindowHandle == nullptr) { - ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId); + ALOGD("No new touched window at (%.1f, %.1f) in display %s", x, y, + displayId.toString().c_str()); // Try to assign the pointer to the first foreground window we find, if there is one. newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle(entry.deviceId); } @@ -2491,8 +2493,8 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (newTouchedWindows.empty()) { ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display " - "%d.", - x, y, displayId); + "%s.", + x, y, displayId.toString().c_str()); outInjectionResult = InputEventInjectionResult::FAILED; return {}; } @@ -2640,9 +2642,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (newTouchedWindowHandle != nullptr && !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) { - ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32, + ALOGI("Touch is slipping out of window %s into window %s in display %s", oldTouchedWindowHandle->getName().c_str(), - newTouchedWindowHandle->getName().c_str(), displayId); + newTouchedWindowHandle->getName().c_str(), displayId.toString().c_str()); // Make a slippery exit from the old window. std::bitset pointerIds; @@ -2827,7 +2829,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Save changes unless the action was scroll in which case the temporary touch // state was only valid for this one action. if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { - if (displayId >= 0) { + if (displayId >= ui::ADISPLAY_ID_DEFAULT) { tempTouchState.clearWindowsWithoutPointers(); mTouchStatesByDisplay[displayId] = tempTouchState; } else { @@ -2842,7 +2844,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( return targets; } -void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) { +void InputDispatcher::finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) { // Prevent stylus interceptor windows from affecting drag and drop behavior for now, until we // have an explicit reason to support it. constexpr bool isStylus = false; @@ -3059,7 +3061,7 @@ void InputDispatcher::addPointerWindowTargetLocked( } void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector& inputTargets, - int32_t displayId) { + ui::LogicalDisplayId displayId) { auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId); if (monitorsIt == mGlobalMonitorsByDisplay.end()) return; @@ -3131,7 +3133,7 @@ static bool canBeObscuredBy(const sp& windowHandle, InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked( const sp& windowHandle, float x, float y) const { const WindowInfo* windowInfo = windowHandle->getInfo(); - int32_t displayId = windowInfo->displayId; + ui::LogicalDisplayId displayId = windowInfo->displayId; const std::vector>& windowHandles = getWindowHandlesLocked(displayId); TouchOcclusionInfo info; info.hasBlockingOcclusion = false; @@ -3215,7 +3217,7 @@ bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionIn bool InputDispatcher::isWindowObscuredAtPointLocked(const sp& windowHandle, float x, float y) const { - int32_t displayId = windowHandle->getInfo()->displayId; + ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId; const std::vector>& windowHandles = getWindowHandlesLocked(displayId); for (const sp& otherHandle : windowHandles) { if (windowHandle == otherHandle) { @@ -3231,7 +3233,7 @@ bool InputDispatcher::isWindowObscuredAtPointLocked(const sp& } bool InputDispatcher::isWindowObscuredLocked(const sp& windowHandle) const { - int32_t displayId = windowHandle->getInfo()->displayId; + ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId; const std::vector>& windowHandles = getWindowHandlesLocked(displayId); const WindowInfo* windowInfo = windowHandle->getInfo(); for (const sp& otherHandle : windowHandles) { @@ -3239,8 +3241,7 @@ bool InputDispatcher::isWindowObscuredLocked(const sp& windowH break; // All future windows are below us. Exit early. } const WindowInfo* otherInfo = otherHandle->getInfo(); - if (canBeObscuredBy(windowHandle, otherHandle) && - otherInfo->overlaps(windowInfo)) { + if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->overlaps(windowInfo)) { return true; } } @@ -3282,7 +3283,7 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { } } - int32_t displayId = getTargetDisplayId(eventEntry); + ui::LogicalDisplayId displayId = getTargetDisplayId(eventEntry); sp focusedWindowHandle = getFocusedWindowHandleLocked(displayId); const WindowInfo* windowDisablingUserActivityInfo = nullptr; if (focusedWindowHandle != nullptr) { @@ -4465,12 +4466,13 @@ void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChange void InputDispatcher::notifyKey(const NotifyKeyArgs& args) { ALOGD_IF(debugInboundEventDetails(), "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64 - ", deviceId=%d, source=%s, displayId=%" PRId32 - "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, " + ", deviceId=%d, source=%s, displayId=%s, policyFlags=0x%x, action=%s, flags=0x%x, " + "keyCode=%s, scanCode=0x%x, metaState=0x%x, " "downTime=%" PRId64, args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(), - args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags, - KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime); + args.displayId.toString().c_str(), args.policyFlags, + KeyEvent::actionToString(args.action), args.flags, KeyEvent::getLabel(args.keyCode), + args.scanCode, args.metaState, args.downTime); Result keyCheck = validateKeyEvent(args.action); if (!keyCheck.ok()) { LOG(ERROR) << "invalid key event: " << keyCheck.error(); @@ -4546,15 +4548,15 @@ bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { if (debugInboundEventDetails()) { ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, " - "displayId=%" PRId32 ", policyFlags=0x%x, " + "displayId=%s, policyFlags=0x%x, " "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, " "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, " "yCursorPosition=%f, downTime=%" PRId64, args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(), - args.displayId, args.policyFlags, MotionEvent::actionToString(args.action).c_str(), - args.actionButton, args.flags, args.metaState, args.buttonState, args.edgeFlags, - args.xPrecision, args.yPrecision, args.xCursorPosition, args.yCursorPosition, - args.downTime); + args.displayId.toString().c_str(), args.policyFlags, + MotionEvent::actionToString(args.action).c_str(), args.actionButton, args.flags, + args.metaState, args.buttonState, args.edgeFlags, args.xPrecision, args.yPrecision, + args.xCursorPosition, args.yCursorPosition, args.downTime); for (uint32_t i = 0; i < args.getPointerCount(); i++) { ALOGD(" Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, " "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f", @@ -4583,7 +4585,8 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) { if (DEBUG_VERIFY_EVENTS) { auto [it, _] = mVerifiersByDisplay.try_emplace(args.displayId, - StringPrintf("display %" PRId32, args.displayId)); + StringPrintf("display %s", + args.displayId.toString().c_str())); Result result = it->second.processMovement(args.deviceId, args.source, args.action, args.getPointerCount(), args.pointerProperties.data(), @@ -4857,8 +4860,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev const bool isPointerEvent = isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER); // If a pointer event has no displayId specified, inject it to the default display. - const int32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE) - ? ADISPLAY_ID_DEFAULT + const ui::LogicalDisplayId displayId = + isPointerEvent && (event->getDisplayId() == ui::ADISPLAY_ID_NONE) + ? ui::ADISPLAY_ID_DEFAULT : event->getDisplayId(); int32_t flags = motionEvent.getFlags(); @@ -5125,22 +5129,21 @@ void InputDispatcher::decrementPendingForegroundDispatches(const EventEntry& ent } const std::vector>& InputDispatcher::getWindowHandlesLocked( - int32_t displayId) const { + ui::LogicalDisplayId displayId) const { static const std::vector> EMPTY_WINDOW_HANDLES; auto it = mWindowHandlesByDisplay.find(displayId); return it != mWindowHandlesByDisplay.end() ? it->second : EMPTY_WINDOW_HANDLES; } sp InputDispatcher::getWindowHandleLocked( - const sp& windowHandleToken, std::optional displayId) const { + const sp& windowHandleToken, std::optional displayId) const { if (windowHandleToken == nullptr) { return nullptr; } if (!displayId) { // Look through all displays. - for (auto& it : mWindowHandlesByDisplay) { - const std::vector>& windowHandles = it.second; + for (const auto& [_, windowHandles] : mWindowHandlesByDisplay) { for (const sp& windowHandle : windowHandles) { if (windowHandle->getToken() == windowHandleToken) { return windowHandle; @@ -5161,16 +5164,15 @@ sp InputDispatcher::getWindowHandleLocked( sp InputDispatcher::getWindowHandleLocked( const sp& windowHandle) const { - for (auto& it : mWindowHandlesByDisplay) { - const std::vector>& windowHandles = it.second; + for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) { for (const sp& handle : windowHandles) { if (handle->getId() == windowHandle->getId() && handle->getToken() == windowHandle->getToken()) { - if (windowHandle->getInfo()->displayId != it.first) { - ALOGE("Found window %s in display %" PRId32 - ", but it should belong to display %" PRId32, - windowHandle->getName().c_str(), it.first, - windowHandle->getInfo()->displayId); + if (windowHandle->getInfo()->displayId != displayId) { + ALOGE("Found window %s in display %s" + ", but it should belong to display %s", + windowHandle->getName().c_str(), displayId.toString().c_str(), + windowHandle->getInfo()->displayId.toString().c_str()); } return handle; } @@ -5179,12 +5181,13 @@ sp InputDispatcher::getWindowHandleLocked( return nullptr; } -sp InputDispatcher::getFocusedWindowHandleLocked(int displayId) const { +sp InputDispatcher::getFocusedWindowHandleLocked( + ui::LogicalDisplayId displayId) const { sp focusedToken = mFocusResolver.getFocusedWindowToken(displayId); return getWindowHandleLocked(focusedToken, displayId); } -ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const { +ui::Transform InputDispatcher::getTransformLocked(ui::LogicalDisplayId displayId) const { auto displayInfoIt = mDisplayInfos.find(displayId); return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform : kIdentityTransform; @@ -5253,7 +5256,8 @@ bool InputDispatcher::canWindowReceiveMotionLocked(const sp& w } void InputDispatcher::updateWindowHandlesForDisplayLocked( - const std::vector>& windowInfoHandles, int32_t displayId) { + const std::vector>& windowInfoHandles, + ui::LogicalDisplayId displayId) { if (windowInfoHandles.empty()) { // Remove all handles on a display if there are no windows left. mWindowHandlesByDisplay.erase(displayId); @@ -5285,13 +5289,14 @@ void InputDispatcher::updateWindowHandlesForDisplayLocked( } if (info->displayId != displayId) { - ALOGE("Window %s updated by wrong display %d, should belong to display %d", - handle->getName().c_str(), displayId, info->displayId); + ALOGE("Window %s updated by wrong display %s, should belong to display %s", + handle->getName().c_str(), displayId.toString().c_str(), + info->displayId.toString().c_str()); continue; } if ((oldHandlesById.find(handle->getId()) != oldHandlesById.end()) && - (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) { + (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) { const sp& oldHandle = oldHandlesById.at(handle->getId()); oldHandle->updateFrom(handle); newHandles.push_back(oldHandle); @@ -5312,7 +5317,8 @@ void InputDispatcher::updateWindowHandlesForDisplayLocked( * For removed handle, check if need to send a cancel event if already in touch. */ void InputDispatcher::setInputWindowsLocked( - const std::vector>& windowInfoHandles, int32_t displayId) { + const std::vector>& windowInfoHandles, + ui::LogicalDisplayId displayId) { if (DEBUG_FOCUS) { std::string windowList; for (const sp& iwh : windowInfoHandles) { @@ -5417,9 +5423,10 @@ void InputDispatcher::setInputWindowsLocked( } void InputDispatcher::setFocusedApplication( - int32_t displayId, const std::shared_ptr& inputApplicationHandle) { + ui::LogicalDisplayId displayId, + const std::shared_ptr& inputApplicationHandle) { if (DEBUG_FOCUS) { - ALOGD("setFocusedApplication displayId=%" PRId32 " %s", displayId, + ALOGD("setFocusedApplication displayId=%s %s", displayId.toString().c_str(), inputApplicationHandle ? inputApplicationHandle->getName().c_str() : ""); } { // acquire lock @@ -5432,7 +5439,8 @@ void InputDispatcher::setFocusedApplication( } void InputDispatcher::setFocusedApplicationLocked( - int32_t displayId, const std::shared_ptr& inputApplicationHandle) { + ui::LogicalDisplayId displayId, + const std::shared_ptr& inputApplicationHandle) { std::shared_ptr oldFocusedApplicationHandle = getValueByKey(mFocusedApplicationHandlesByDisplay, displayId); @@ -5469,9 +5477,9 @@ void InputDispatcher::setMinTimeBetweenUserActivityPokes(std::chrono::millisecon * cancel all the unreleased display-unspecified events for the focused window on the old focused * display. The display-specified events won't be affected. */ -void InputDispatcher::setFocusedDisplay(int32_t displayId) { +void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) { if (DEBUG_FOCUS) { - ALOGD("setFocusedDisplay displayId=%" PRId32, displayId); + ALOGD("setFocusedDisplay displayId=%s", displayId.toString().c_str()); } { // acquire lock std::scoped_lock _l(mLock); @@ -5490,7 +5498,7 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, "The display which contains this window no longer has focus.", traceContext.getTracker()); - options.displayId = ADISPLAY_ID_NONE; + options.displayId = ui::ADISPLAY_ID_NONE; synthesizeCancelationEventsForWindowLocked(windowHandle, options); } mFocusedDisplayId = displayId; @@ -5500,7 +5508,8 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) { sendFocusChangedCommandLocked(oldFocusedWindowToken, newFocusedWindowToken); if (newFocusedWindowToken == nullptr) { - ALOGW("Focused display #%" PRId32 " does not have a focused window.", displayId); + ALOGW("Focused display #%s does not have a focused window.", + displayId.toString().c_str()); if (mFocusResolver.hasFocusedWindowTokens()) { ALOGE("But another display has a focused window\n%s", mFocusResolver.dumpFocusedWindows().c_str()); @@ -5566,15 +5575,15 @@ void InputDispatcher::setInputFilterEnabled(bool enabled) { } bool InputDispatcher::setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, - bool hasPermission, int32_t displayId) { + bool hasPermission, ui::LogicalDisplayId displayId) { bool needWake = false; { std::scoped_lock lock(mLock); ALOGD_IF(DEBUG_TOUCH_MODE, "Request to change touch mode to %s (calling pid=%s, uid=%s, " - "hasPermission=%s, target displayId=%d, mTouchModePerDisplay[displayId]=%s)", + "hasPermission=%s, target displayId=%s, mTouchModePerDisplay[displayId]=%s)", toString(inTouchMode), pid.toString().c_str(), uid.toString().c_str(), - toString(hasPermission), displayId, + toString(hasPermission), displayId.toString().c_str(), mTouchModePerDisplay.count(displayId) == 0 ? "not set" : std::to_string(mTouchModePerDisplay[displayId]).c_str()); @@ -5632,7 +5641,7 @@ void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) { mMaximumObscuringOpacityForTouch = opacity; } -std::tuple +std::tuple InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp& token) { for (auto& [displayId, state] : mTouchStatesByDisplay) { for (TouchedWindow& w : state.windows) { @@ -5641,7 +5650,7 @@ InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp& token) } } } - return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT); + return std::make_tuple(nullptr, nullptr, ui::ADISPLAY_ID_DEFAULT); } bool InputDispatcher::transferTouchGesture(const sp& fromToken, const sp& toToken, @@ -5748,10 +5757,11 @@ bool InputDispatcher::transferTouchGesture(const sp& fromToken, const s * Return null if there are no windows touched on that display, or if more than one foreground * window is being touched. */ -sp InputDispatcher::findTouchedForegroundWindowLocked(int32_t displayId) const { +sp InputDispatcher::findTouchedForegroundWindowLocked( + ui::LogicalDisplayId displayId) const { auto stateIt = mTouchStatesByDisplay.find(displayId); if (stateIt == mTouchStatesByDisplay.end()) { - ALOGI("No touch state on display %" PRId32, displayId); + ALOGI("No touch state on display %s", displayId.toString().c_str()); return nullptr; } @@ -5774,14 +5784,14 @@ sp InputDispatcher::findTouchedForegroundWindowLocked(int32_t // Binder call bool InputDispatcher::transferTouchOnDisplay(const sp& destChannelToken, - int32_t displayId) { + ui::LogicalDisplayId displayId) { sp fromToken; { // acquire lock std::scoped_lock _l(mLock); sp toWindowHandle = getWindowHandleLocked(destChannelToken, displayId); if (toWindowHandle == nullptr) { - ALOGW("Could not find window associated with token=%p on display %" PRId32, - destChannelToken.get(), displayId); + ALOGW("Could not find window associated with token=%p on display %s", + destChannelToken.get(), displayId.toString().c_str()); return false; } @@ -5850,18 +5860,19 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled)); dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen)); dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled)); - dump += StringPrintf(INDENT "FocusedDisplayId: %" PRId32 "\n", mFocusedDisplayId); + dump += StringPrintf(INDENT "FocusedDisplayId: %s\n", mFocusedDisplayId.toString().c_str()); if (!mFocusedApplicationHandlesByDisplay.empty()) { dump += StringPrintf(INDENT "FocusedApplications:\n"); for (auto& it : mFocusedApplicationHandlesByDisplay) { - const int32_t displayId = it.first; + const ui::LogicalDisplayId displayId = it.first; const std::shared_ptr& applicationHandle = it.second; const std::chrono::duration timeout = applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); - dump += StringPrintf(INDENT2 "displayId=%" PRId32 - ", name='%s', dispatchingTimeout=%" PRId64 "ms\n", - displayId, applicationHandle->getName().c_str(), millis(timeout)); + dump += StringPrintf(INDENT2 "displayId=%s, name='%s', dispatchingTimeout=%" PRId64 + "ms\n", + displayId.toString().c_str(), applicationHandle->getName().c_str(), + millis(timeout)); } } else { dump += StringPrintf(INDENT "FocusedApplications: \n"); @@ -5874,7 +5885,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { dump += StringPrintf(INDENT "TouchStatesByDisplay:\n"); for (const auto& [displayId, state] : mTouchStatesByDisplay) { std::string touchStateDump = addLinePrefix(state.dump(), INDENT2); - dump += INDENT2 + std::to_string(displayId) + " : " + touchStateDump; + dump += INDENT2 + displayId.toString() + " : " + touchStateDump; } } else { dump += INDENT "TouchStates: \n"; @@ -5887,7 +5898,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { if (!mWindowHandlesByDisplay.empty()) { for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) { - dump += StringPrintf(INDENT "Display: %" PRId32 "\n", displayId); + dump += StringPrintf(INDENT "Display: %s\n", displayId.toString().c_str()); if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) { const auto& displayInfo = it->second; dump += StringPrintf(INDENT2 "logicalSize=%dx%d\n", displayInfo.logicalWidth, @@ -5913,7 +5924,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { if (!mGlobalMonitorsByDisplay.empty()) { for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) { - dump += StringPrintf(INDENT "Global monitors on display %d:\n", displayId); + dump += StringPrintf(INDENT "Global monitors on display %s:\n", + displayId.toString().c_str()); dumpMonitors(dump, monitors); } } else { @@ -6002,8 +6014,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const { if (!mTouchModePerDisplay.empty()) { dump += INDENT "TouchModePerDisplay:\n"; for (const auto& [displayId, touchMode] : mTouchModePerDisplay) { - dump += StringPrintf(INDENT2 "Display: %" PRId32 " TouchMode: %s\n", displayId, - std::to_string(touchMode).c_str()); + dump += StringPrintf(INDENT2 "Display: %s TouchMode: %s\n", + displayId.toString().c_str(), std::to_string(touchMode).c_str()); } } else { dump += INDENT "TouchModePerDisplay: \n"; @@ -6076,9 +6088,8 @@ Result> InputDispatcher::createInputChannel(const return clientChannel; } -Result> InputDispatcher::createInputMonitor(int32_t displayId, - const std::string& name, - gui::Pid pid) { +Result> InputDispatcher::createInputMonitor( + ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) { std::unique_ptr serverChannel; std::unique_ptr clientChannel; status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); @@ -6089,7 +6100,7 @@ Result> InputDispatcher::createInputMonitor(int32_ { // acquire lock std::scoped_lock _l(mLock); - if (displayId < 0) { + if (displayId < ui::ADISPLAY_ID_DEFAULT) { return base::Error(BAD_VALUE) << "Attempted to create input monitor with name " << name << " without a specified display."; } @@ -6274,7 +6285,8 @@ void InputDispatcher::requestPointerCapture(const sp& windowToken, bool mLooper->wake(); } -void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId, bool isEligible) { +void InputDispatcher::setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId, + bool isEligible) { { // acquire lock std::scoped_lock _l(mLock); std::erase(mIneligibleDisplaysForPointerCapture, displayId); @@ -6850,7 +6862,9 @@ void InputDispatcher::setFocusedWindow(const FocusRequest& request) { { // acquire lock std::scoped_lock _l(mLock); std::optional changes = - mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId)); + mFocusResolver.setFocusedWindow(request, + getWindowHandlesLocked( + ui::LogicalDisplayId{request.displayId})); ScopedSyntheticEventTracer traceContext(mTracer); if (changes) { onFocusChangedLocked(*changes, traceContext.getTracker()); @@ -6934,7 +6948,7 @@ void InputDispatcher::setPointerCaptureLocked(const sp& windowToken) { postCommandLocked(std::move(command)); } -void InputDispatcher::displayRemoved(int32_t displayId) { +void InputDispatcher::displayRemoved(ui::LogicalDisplayId displayId) { { // acquire lock std::scoped_lock _l(mLock); // Set an empty list to remove all handles from the specific display. @@ -6966,7 +6980,7 @@ void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) }; // The listener sends the windows as a flattened array. Separate the windows by display for // more convenient parsing. - std::unordered_map>> handlesPerDisplay; + std::unordered_map>> handlesPerDisplay; for (const auto& info : update.windowInfos) { handlesPerDisplay.emplace(info.displayId, std::vector>()); handlesPerDisplay[info.displayId].push_back(sp::make(info)); @@ -7008,10 +7022,10 @@ bool InputDispatcher::shouldDropInput( WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED) && isWindowObscuredLocked(windowHandle))) { ALOGW("Dropping %s event targeting %s as requested by the input configuration {%s} on " - "display %" PRId32 ".", + "display %s.", ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(), windowHandle->getInfo()->inputConfig.string().c_str(), - windowHandle->getInfo()->displayId); + windowHandle->getInfo()->displayId.toString().c_str()); return true; } return false; @@ -7155,8 +7169,9 @@ void InputDispatcher::setKeyRepeatConfiguration(std::chrono::nanoseconds timeout mConfig.keyRepeatDelay = delay.count(); } -bool InputDispatcher::isPointerInWindow(const sp& token, int32_t displayId, - DeviceId deviceId, int32_t pointerId) { +bool InputDispatcher::isPointerInWindow(const sp& token, + ui::LogicalDisplayId displayId, DeviceId deviceId, + int32_t pointerId) { std::scoped_lock _l(mLock); auto touchStateIt = mTouchStatesByDisplay.find(displayId); if (touchStateIt == mTouchStatesByDisplay.end()) { diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 3d127c296a..f671ea71f4 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -114,35 +114,37 @@ public: std::unique_ptr verifyInputEvent(const InputEvent& event) override; void setFocusedApplication( - int32_t displayId, + ui::LogicalDisplayId displayId, const std::shared_ptr& inputApplicationHandle) override; - void setFocusedDisplay(int32_t displayId) override; + void setFocusedDisplay(ui::LogicalDisplayId displayId) override; void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) override; void setInputDispatchMode(bool enabled, bool frozen) override; void setInputFilterEnabled(bool enabled) override; bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission, - int32_t displayId) override; + ui::LogicalDisplayId displayId) override; void setMaximumObscuringOpacityForTouch(float opacity) override; bool transferTouchGesture(const sp& fromToken, const sp& toToken, bool isDragDrop = false) override; - bool transferTouchOnDisplay(const sp& destChannelToken, int32_t displayId) override; + bool transferTouchOnDisplay(const sp& destChannelToken, + ui::LogicalDisplayId displayId) override; base::Result> createInputChannel( const std::string& name) override; void setFocusedWindow(const android::gui::FocusRequest&) override; - base::Result> createInputMonitor(int32_t displayId, + base::Result> createInputMonitor(ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) override; status_t removeInputChannel(const sp& connectionToken) override; status_t pilferPointers(const sp& token) override; void requestPointerCapture(const sp& windowToken, bool enabled) override; bool flushSensor(int deviceId, InputDeviceSensorType sensorType) override; - void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) override; + void setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId, + bool isEligible) override; std::array sign(const VerifiedInputEvent& event) const; - void displayRemoved(int32_t displayId) override; + void displayRemoved(ui::LogicalDisplayId displayId) override; // Public because it's also used by tests to simulate the WindowInfosListener callback void onWindowInfosChanged(const gui::WindowInfosUpdate&); @@ -155,8 +157,8 @@ public: void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout, std::chrono::nanoseconds delay) override; - bool isPointerInWindow(const sp& token, int32_t displayId, DeviceId deviceId, - int32_t pointerId) override; + bool isPointerInWindow(const sp& token, ui::LogicalDisplayId displayId, + DeviceId deviceId, int32_t pointerId) override; void setInputMethodConnectionIsActive(bool isActive) override; @@ -249,17 +251,17 @@ private: std::shared_ptr mNextUnblockedEvent GUARDED_BY(mLock); sp findTouchedWindowAtLocked( - int32_t displayId, float x, float y, bool isStylus = false, + ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false, bool ignoreDragWindow = false) const REQUIRES(mLock); std::vector findOutsideTargetsLocked( - int32_t displayId, const sp& touchedWindow, + ui::LogicalDisplayId displayId, const sp& touchedWindow, int32_t pointerId) const REQUIRES(mLock); std::vector> findTouchedSpyWindowsAtLocked( - int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock); + ui::LogicalDisplayId displayId, float x, float y, bool isStylus) const REQUIRES(mLock); - sp findTouchedForegroundWindowLocked(int32_t displayId) const - REQUIRES(mLock); + sp findTouchedForegroundWindowLocked( + ui::LogicalDisplayId displayId) const REQUIRES(mLock); std::shared_ptr getConnectionLocked(const sp& inputConnectionToken) const REQUIRES(mLock); @@ -283,7 +285,8 @@ private: std::optional findMonitorPidByTokenLocked(const sp& token) REQUIRES(mLock); // Input channels that will receive a copy of all input events sent to the provided display. - std::unordered_map> mGlobalMonitorsByDisplay GUARDED_BY(mLock); + std::unordered_map> mGlobalMonitorsByDisplay + GUARDED_BY(mLock); const HmacKeyManager mHmacKeyManager; const std::array getSignature(const MotionEntry& motionEntry, @@ -342,7 +345,8 @@ private: // This map is not really needed, but it helps a lot with debugging (dumpsys input). // In the java layer, touch mode states are spread across multiple DisplayContent objects, // making harder to snapshot and retrieve them. - std::map mTouchModePerDisplay GUARDED_BY(mLock); + std::map mTouchModePerDisplay + GUARDED_BY(mLock); class DispatcherWindowListener : public gui::WindowInfosListener { public: @@ -354,25 +358,26 @@ private: }; sp mWindowInfoListener; - std::unordered_map>> + std::unordered_map>> mWindowHandlesByDisplay GUARDED_BY(mLock); - std::unordered_map mDisplayInfos + std::unordered_map mDisplayInfos GUARDED_BY(mLock); void setInputWindowsLocked( const std::vector>& inputWindowHandles, - int32_t displayId) REQUIRES(mLock); + ui::LogicalDisplayId displayId) REQUIRES(mLock); // Get a reference to window handles by display, return an empty vector if not found. const std::vector>& getWindowHandlesLocked( - int32_t displayId) const REQUIRES(mLock); - ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock); + ui::LogicalDisplayId displayId) const REQUIRES(mLock); + ui::Transform getTransformLocked(ui::LogicalDisplayId displayId) const REQUIRES(mLock); sp getWindowHandleLocked( - const sp& windowHandleToken, std::optional displayId = {}) const - REQUIRES(mLock); + const sp& windowHandleToken, + std::optional displayId = {}) const REQUIRES(mLock); sp getWindowHandleLocked( const sp& windowHandle) const REQUIRES(mLock); - sp getFocusedWindowHandleLocked(int displayId) const - REQUIRES(mLock); + sp getFocusedWindowHandleLocked( + ui::LogicalDisplayId displayId) const REQUIRES(mLock); bool canWindowReceiveMotionLocked(const sp& window, const MotionEntry& motionEntry) const REQUIRES(mLock); @@ -387,20 +392,21 @@ private: */ void updateWindowHandlesForDisplayLocked( const std::vector>& inputWindowHandles, - int32_t displayId) REQUIRES(mLock); + ui::LogicalDisplayId displayId) REQUIRES(mLock); - std::unordered_map mTouchStatesByDisplay GUARDED_BY(mLock); + std::unordered_map mTouchStatesByDisplay + GUARDED_BY(mLock); std::unique_ptr mDragState GUARDED_BY(mLock); void setFocusedApplicationLocked( - int32_t displayId, + ui::LogicalDisplayId displayId, const std::shared_ptr& inputApplicationHandle) REQUIRES(mLock); // Focused applications. - std::unordered_map> + std::unordered_map> mFocusedApplicationHandlesByDisplay GUARDED_BY(mLock); // Top focused display. - int32_t mFocusedDisplayId GUARDED_BY(mLock); + ui::LogicalDisplayId mFocusedDisplayId GUARDED_BY(mLock); // Keeps track of the focused window per display and determines focus changes. FocusResolver mFocusResolver GUARDED_BY(mLock); @@ -417,7 +423,8 @@ private: // Displays that are ineligible for pointer capture. // TODO(b/214621487): Remove or move to a display flag. - std::vector mIneligibleDisplaysForPointerCapture GUARDED_BY(mLock); + std::vector mIneligibleDisplaysForPointerCapture + GUARDED_BY(mLock); // Disable Pointer Capture as a result of loss of window focus. void disablePointerCaptureForcedLocked() REQUIRES(mLock); @@ -492,7 +499,7 @@ private: /** * The displayId that the focused application is associated with. */ - int32_t mAwaitedApplicationDisplayId GUARDED_BY(mLock); + ui::LogicalDisplayId mAwaitedApplicationDisplayId GUARDED_BY(mLock); void processNoFocusedWindowAnrLocked() REQUIRES(mLock); /** @@ -526,7 +533,7 @@ private: // shade is pulled down while we are counting down the timeout). void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock); - int32_t getTargetDisplayId(const EventEntry& entry); + ui::LogicalDisplayId getTargetDisplayId(const EventEntry& entry); sp findFocusedWindowTargetLocked( nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime, android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock); @@ -551,13 +558,13 @@ private: std::bitset pointerIds, std::optional firstDownTimeInTarget, std::vector& inputTargets) const REQUIRES(mLock); - void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, int32_t displayId) - REQUIRES(mLock); + void addGlobalMonitoringTargetsLocked(std::vector& inputTargets, + ui::LogicalDisplayId displayId) REQUIRES(mLock); void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock); // Enqueue a drag event if needed, and update the touch state. // Uses findTouchedWindowTargetsLocked to make the decision void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock); - void finishDragAndDrop(int32_t displayId, float x, float y) REQUIRES(mLock); + void finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) REQUIRES(mLock); struct TouchOcclusionInfo { bool hasBlockingOcclusion; @@ -675,14 +682,14 @@ private: const std::string& reason) REQUIRES(mLock); void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason) REQUIRES(mLock); - std::map mVerifiersByDisplay; + std::map mVerifiersByDisplay; // Returns a fallback KeyEntry that should be sent to the connection, if required. std::unique_ptr afterKeyEventLockedInterruptable( const std::shared_ptr& connection, DispatchEntry& dispatchEntry, const KeyEntry& keyEntry, bool handled) REQUIRES(mLock); // Find touched state and touched window by token. - std::tuple + std::tuple findTouchStateWindowAndDisplayLocked(const sp& token) REQUIRES(mLock); // Statistics gathering. diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 02bc3680bf..46e7e8b35e 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -28,7 +28,8 @@ InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerato InputState::~InputState() {} -bool InputState::isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const { +bool InputState::isHovering(DeviceId deviceId, uint32_t source, + ui::LogicalDisplayId displayId) const { for (const MotionMemento& memento : mMotionMementos) { if (memento.deviceId == deviceId && memento.source == source && memento.displayId == displayId && memento.hovering) { diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index d49469ddc1..ab83ef911d 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -36,7 +36,7 @@ public: // Returns true if the specified source is known to have received a hover enter // motion event. - bool isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const; + bool isHovering(DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId) const; // Records tracking information for a key event that has just been published. // Returns true if the event should be delivered, false if it is inconsistent @@ -90,7 +90,7 @@ private: struct KeyMemento { DeviceId deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; int32_t keyCode; int32_t scanCode; int32_t metaState; @@ -102,7 +102,7 @@ private: struct MotionMemento { DeviceId deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; int32_t flags; float xPrecision; float yPrecision; diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h index 60a75ee107..90374f1481 100644 --- a/services/inputflinger/dispatcher/InputTarget.h +++ b/services/inputflinger/dispatcher/InputTarget.h @@ -18,7 +18,6 @@ #include #include -#include #include #include #include diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h index 6b9262cfa3..653f595670 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h @@ -92,14 +92,14 @@ public: * This method may be called on any thread (usually by the input manager). */ virtual void setFocusedApplication( - int32_t displayId, + ui::LogicalDisplayId displayId, const std::shared_ptr& inputApplicationHandle) = 0; /* Sets the focused display. * * This method may be called on any thread (usually by the input manager). */ - virtual void setFocusedDisplay(int32_t displayId) = 0; + virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0; /** Sets the minimum time between user activity pokes. */ virtual void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) = 0; @@ -130,7 +130,7 @@ public: * Returns true when changing touch mode state. */ virtual bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission, - int32_t displayId) = 0; + ui::LogicalDisplayId displayId) = 0; /** * Sets the maximum allowed obscuring opacity by UID to propagate touches. @@ -156,7 +156,8 @@ public: * Returns true on success, false if there was no on-going touch on the display. * @deprecated */ - virtual bool transferTouchOnDisplay(const sp& destChannelToken, int32_t displayId) = 0; + virtual bool transferTouchOnDisplay(const sp& destChannelToken, + ui::LogicalDisplayId displayId) = 0; /** * Sets focus on the specified window. @@ -179,9 +180,8 @@ public: * * This method may be called on any thread (usually by the input manager). */ - virtual base::Result> createInputMonitor(int32_t displayId, - const std::string& name, - gui::Pid pid) = 0; + virtual base::Result> createInputMonitor( + ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) = 0; /* Removes input channels that will no longer receive input events. * @@ -207,7 +207,8 @@ public: * ineligible, all attempts to request pointer capture for windows on that display will fail. * TODO(b/214621487): Remove or move to a display flag. */ - virtual void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) = 0; + virtual void setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId, + bool isEligible) = 0; /* Flush input device motion sensor. * @@ -218,7 +219,7 @@ public: /** * Called when a display has been removed from the system. */ - virtual void displayRemoved(int32_t displayId) = 0; + virtual void displayRemoved(ui::LogicalDisplayId displayId) = 0; /* * Abort the current touch stream. @@ -234,8 +235,8 @@ public: /* * Determine if a pointer from a device is being dispatched to the given window. */ - virtual bool isPointerInWindow(const sp& token, int32_t displayId, DeviceId deviceId, - int32_t pointerId) = 0; + virtual bool isPointerInWindow(const sp& token, ui::LogicalDisplayId displayId, + DeviceId deviceId, int32_t pointerId) = 0; /* * Notify the dispatcher that the state of the input method connection changed. diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 62c2b02967..0f03620f05 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -99,8 +99,9 @@ public: * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event * should be dispatched to applications. */ - virtual void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action, - nsecs_t when, uint32_t& policyFlags) = 0; + virtual void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source, + int32_t action, nsecs_t when, + uint32_t& policyFlags) = 0; /* Allows the policy a chance to intercept a key before dispatching. */ virtual nsecs_t interceptKeyBeforeDispatching(const sp& token, @@ -119,7 +120,8 @@ public: uint32_t policyFlags) = 0; /* Poke user activity for an event dispatched to a window. */ - virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0; + virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) = 0; /* * Return true if the provided event is stale, and false otherwise. Used for determining diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp index c431fb726a..2d7554c9c1 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp @@ -47,7 +47,7 @@ void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent outProto.set_source(event.source); outProto.set_action(event.action); outProto.set_device_id(event.deviceId); - outProto.set_display_id(event.displayId); + outProto.set_display_id(event.displayId.val()); outProto.set_classification(static_cast(event.classification)); outProto.set_flags(event.flags); outProto.set_policy_flags(event.policyFlags); @@ -88,7 +88,7 @@ void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& even outProto.set_source(event.source); outProto.set_action(event.action); outProto.set_device_id(event.deviceId); - outProto.set_display_id(event.displayId); + outProto.set_display_id(event.displayId.val()); outProto.set_repeat_count(event.repeatCount); outProto.set_flags(event.flags); outProto.set_policy_flags(event.policyFlags); diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 2b45e3ae1c..25099c3199 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -50,7 +50,7 @@ struct TracedKeyEvent { uint32_t policyFlags; int32_t deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId; int32_t action; int32_t keyCode; int32_t scanCode; @@ -70,7 +70,7 @@ struct TracedMotionEvent { uint32_t policyFlags; int32_t deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId; int32_t action; int32_t actionButton; int32_t flags; diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 4d8ed99eb4..e5c3aa08d1 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -125,7 +125,7 @@ struct InputReaderConfiguration { std::unordered_map keyboardLayoutAssociations; // The suggested display ID to show the cursor. - int32_t defaultPointerDisplayId; + ui::LogicalDisplayId defaultPointerDisplayId; // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest). // @@ -135,7 +135,7 @@ struct InputReaderConfiguration { // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice. // // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled. - std::set displaysWithMousePointerAccelerationDisabled; + std::set displaysWithMousePointerAccelerationDisabled; // Velocity control parameters for mouse pointer movements. // @@ -243,6 +243,7 @@ struct InputReaderConfiguration { InputReaderConfiguration() : virtualKeyQuietTime(0), + defaultPointerDisplayId(ui::ADISPLAY_ID_DEFAULT), mousePointerSpeed(0), displaysWithMousePointerAccelerationDisabled(), pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, @@ -275,7 +276,7 @@ struct InputReaderConfiguration { std::optional getDisplayViewportByUniqueId(const std::string& uniqueDisplayId) const; std::optional getDisplayViewportByPort(uint8_t physicalPort) const; - std::optional getDisplayViewportById(int32_t displayId) const; + std::optional getDisplayViewportById(ui::LogicalDisplayId displayId) const; void setDisplayViewports(const std::vector& viewports); void dump(std::string& dump) const; @@ -366,7 +367,7 @@ public: virtual std::vector getSensors(int32_t deviceId) = 0; /* Return true if the device can send input events to the specified display. */ - virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0; + virtual bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) = 0; /* Enable sensor in input reader mapper. */ virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, @@ -471,7 +472,7 @@ public: * be used as the range of possible values for pointing devices, like mice and touchpads. */ virtual std::optional getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0; + ui::LogicalDisplayId associatedDisplayId = ui::ADISPLAY_ID_NONE) = 0; }; } // namespace android diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h index 736b1e07b7..865f3d0302 100644 --- a/services/inputflinger/include/NotifyArgs.h +++ b/services/inputflinger/include/NotifyArgs.h @@ -61,7 +61,7 @@ struct NotifyKeyArgs { int32_t deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; uint32_t policyFlags; int32_t action; int32_t flags; @@ -74,9 +74,9 @@ struct NotifyKeyArgs { inline NotifyKeyArgs() {} NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, - uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, - int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, - nsecs_t downTime); + uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags, + int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, + int32_t metaState, nsecs_t downTime); bool operator==(const NotifyKeyArgs& rhs) const = default; @@ -91,7 +91,7 @@ struct NotifyMotionArgs { int32_t deviceId; uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; uint32_t policyFlags; int32_t action; int32_t actionButton; @@ -123,12 +123,12 @@ struct NotifyMotionArgs { inline NotifyMotionArgs() {} NotifyMotionArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, - uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action, - int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState, - MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount, - const PointerProperties* pointerProperties, const PointerCoords* pointerCoords, - float xPrecision, float yPrecision, float xCursorPosition, - float yCursorPosition, nsecs_t downTime, + uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags, + int32_t action, int32_t actionButton, int32_t flags, int32_t metaState, + int32_t buttonState, MotionClassification classification, int32_t edgeFlags, + uint32_t pointerCount, const PointerProperties* pointerProperties, + const PointerCoords* pointerCoords, float xPrecision, float yPrecision, + float xCursorPosition, float yCursorPosition, nsecs_t downTime, const std::vector& videoFrames); NotifyMotionArgs(const NotifyMotionArgs& other) = default; diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h index 1bd55958d9..1ba0cfd20a 100644 --- a/services/inputflinger/include/NotifyArgsBuilders.h +++ b/services/inputflinger/include/NotifyArgsBuilders.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include // for nsecs_t, systemTime @@ -55,7 +54,7 @@ public: return *this; } - MotionArgsBuilder& displayId(int32_t displayId) { + MotionArgsBuilder& displayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; return *this; } @@ -151,7 +150,7 @@ private: uint32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mActionButton{0}; int32_t mButtonState{0}; @@ -187,7 +186,7 @@ public: return *this; } - KeyArgsBuilder& displayId(int32_t displayId) { + KeyArgsBuilder& displayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; return *this; } @@ -230,7 +229,7 @@ private: uint32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - int32_t mDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mFlags{0}; int32_t mKeyCode{AKEYCODE_UNKNOWN}; diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h index 462aedc539..f6dc10997a 100644 --- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h +++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h @@ -53,7 +53,8 @@ public: * @param displayId The updated display on which the mouse cursor is shown * @param position The new position of the mouse cursor on the logical display */ - virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0; + virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, + const FloatPoint& position) = 0; }; } // namespace android diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index c1467b3eed..cee44fc6b3 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -125,13 +125,13 @@ public: * pressed (not hovering). */ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) = 0; + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) = 0; /* Removes all spots. */ virtual void clearSpots() = 0; /* Gets the id of the display where the pointer should be shown. */ - virtual int32_t getDisplayId() const = 0; + virtual ui::LogicalDisplayId getDisplayId() const = 0; /* Sets the associated display of this pointer. Pointer should show on that display. */ virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0; @@ -145,7 +145,7 @@ public: /* Sets the flag to skip screenshot of the pointer indicators on the display matching the * provided displayId. */ - virtual void setSkipScreenshot(int32_t displayId, bool skip) = 0; + virtual void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) = 0; }; } // namespace android diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 44dfd15aaa..15586e2c4b 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -442,7 +442,7 @@ std::list InputDevice::updateExternalStylusState(const StylusState& InputDeviceInfo InputDevice::getDeviceInfo() { InputDeviceInfo outDeviceInfo; outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal, - mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE), + mHasMic, getAssociatedDisplayId().value_or(ui::ADISPLAY_ID_NONE), {mShouldSmoothScroll}, isEnabled()); for_each_mapper( @@ -699,14 +699,14 @@ NotifyDeviceResetArgs InputDevice::notifyReset(nsecs_t when) { return NotifyDeviceResetArgs(mContext->getNextId(), when, mId); } -std::optional InputDevice::getAssociatedDisplayId() { +std::optional InputDevice::getAssociatedDisplayId() { // Check if we had associated to the specific display. if (mAssociatedViewport) { return mAssociatedViewport->displayId; } // No associated display port, check if some InputMapper is associated. - return first_in_mappers( + return first_in_mappers( [](InputMapper& mapper) { return mapper.getAssociatedDisplayId(); }); } diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 903c6ed9bc..12f52b899c 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -856,7 +856,7 @@ bool InputReader::isInputDeviceEnabled(int32_t deviceId) { return false; } -bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) { +bool InputReader::canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) { std::scoped_lock _l(mLock); InputDevice* device = findInputDeviceLocked(deviceId); @@ -870,10 +870,9 @@ bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) { return false; } - std::optional associatedDisplayId = device->getAssociatedDisplayId(); + std::optional associatedDisplayId = device->getAssociatedDisplayId(); // No associated display. By default, can dispatch to all displays. - if (!associatedDisplayId || - *associatedDisplayId == ADISPLAY_ID_NONE) { + if (!associatedDisplayId || !associatedDisplayId->isValid()) { return true; } diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index feb407152c..4c9af2e30e 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -131,7 +131,7 @@ public: inline const PropertyMap& getConfiguration() { return mConfiguration; } inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } - std::optional getAssociatedDisplayId(); + std::optional getAssociatedDisplayId(); void updateLedState(bool reset); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 5f882cfcd3..d9ac917031 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -86,7 +86,7 @@ public: std::vector getVibratorIds(int32_t deviceId) override; - bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) override; + bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) override; bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp index 061c6a3e75..21dec6ffa6 100644 --- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -279,7 +278,7 @@ NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs( LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(), "Mismatched coords and properties arrays."); return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, - ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action, + ui::ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action, /*actionButton=*/actionButton, flags, mReaderContext.getGlobalMetaState(), mButtonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(), diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index ede2d72c44..d5fe0400b2 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -129,7 +129,8 @@ void CursorInputMapper::dump(std::string& dump) { mWheelXVelocityControl.getParameters().dump().c_str()); dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale); dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale); - dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str()); + dump += StringPrintf(INDENT3 "DisplayId: %s\n", + toString(mDisplayId, streamableToString).c_str()); dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(mOrientation).c_str()); dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState); dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState))); @@ -417,7 +418,7 @@ int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCod } } -std::optional CursorInputMapper::getAssociatedDisplayId() { +std::optional CursorInputMapper::getAssociatedDisplayId() { return mDisplayId; } @@ -487,13 +488,13 @@ void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfigura if (mEnableNewMousePointerBallistics) { mNewPointerVelocityControl.setAccelerationEnabled( config.displaysWithMousePointerAccelerationDisabled.count( - mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0); + mDisplayId.value_or(ui::ADISPLAY_ID_NONE)) == 0); mNewPointerVelocityControl.setCurve( createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed)); } else { mOldPointerVelocityControl.setParameters( (config.displaysWithMousePointerAccelerationDisabled.count( - mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0) + mDisplayId.value_or(ui::ADISPLAY_ID_NONE)) == 0) ? config.pointerVelocityControlParameters : FLAT_VELOCITY_CONTROL_PARAMS); } @@ -505,7 +506,7 @@ void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfigura void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) { const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; - mDisplayId = ADISPLAY_ID_NONE; + mDisplayId = ui::ADISPLAY_ID_NONE; std::optional resolvedViewport; if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) { // This InputDevice is associated with a viewport. @@ -517,7 +518,7 @@ void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfigurat // Always use DISPLAY_ID_NONE for mouse events. // PointerChoreographer will make it target the correct the displayId later. resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); - mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; + mDisplayId = resolvedViewport ? std::make_optional(ui::ADISPLAY_ID_NONE) : std::nullopt; } mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) || diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 3daae2fe1d..e45105ad3a 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -66,7 +66,7 @@ public: virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; - virtual std::optional getAssociatedDisplayId() override; + virtual std::optional getAssociatedDisplayId() override; private: // Amount that trackball needs to move in order to generate a key event. @@ -115,7 +115,7 @@ private: // The display that events generated by this mapper should target. This can be set to // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. - std::optional mDisplayId; + std::optional mDisplayId; ui::Rotation mOrientation{ui::ROTATION_0}; FloatRect mBoundsInLogicalDisplay{}; diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index 06de4c25e3..c7eea0e980 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -117,7 +117,7 @@ public: [[nodiscard]] virtual std::list updateExternalStylusState(const StylusState& state); - virtual std::optional getAssociatedDisplayId() { return std::nullopt; } + virtual std::optional getAssociatedDisplayId() { return std::nullopt; } virtual void updateLedState(bool reset) {} protected: diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 8a9ea75a97..7fcff951b0 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -341,7 +341,7 @@ std::list JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, // button will likely wake the device. // TODO: Use the input device configuration to control this behavior more finely. uint32_t policyFlags = 0; - int32_t displayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; if (getDeviceContext().getAssociatedViewport()) { displayId = getDeviceContext().getAssociatedViewport()->displayId; } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 658ceabcc3..be8d183215 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -110,11 +110,11 @@ ui::Rotation KeyboardInputMapper::getOrientation() { return ui::ROTATION_0; } -int32_t KeyboardInputMapper::getDisplayId() { +ui::LogicalDisplayId KeyboardInputMapper::getDisplayId() { if (mViewport) { return mViewport->displayId; } - return ADISPLAY_ID_NONE; + return ui::ADISPLAY_ID_NONE; } std::optional KeyboardInputMapper::getKeyboardLayoutInfo() const { @@ -457,7 +457,7 @@ void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState, int32_t } } -std::optional KeyboardInputMapper::getAssociatedDisplayId() { +std::optional KeyboardInputMapper::getAssociatedDisplayId() { if (mViewport) { return std::make_optional(mViewport->displayId); } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index 500256b21f..f2d3f4d7d8 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -46,7 +46,7 @@ public: int32_t getMetaState() override; bool updateMetaState(int32_t keyCode) override; - std::optional getAssociatedDisplayId() override; + std::optional getAssociatedDisplayId() override; void updateLedState(bool reset) override; private: @@ -91,7 +91,7 @@ private: void dumpParameters(std::string& dump) const; ui::Rotation getOrientation(); - int32_t getDisplayId(); + ui::LogicalDisplayId getDisplayId(); [[nodiscard]] std::list processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode, int32_t usageCode); diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 07ae5b1cac..a3206c2844 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -125,7 +125,7 @@ std::list RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readT if (scrolled) { int32_t metaState = getContext()->getGlobalMetaState(); // This is not a pointer, so it's not associated with a display. - int32_t displayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; if (mOrientation == ui::ROTATION_180) { scroll = -scroll; diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp index c12e95dfa0..d60dc5546b 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp @@ -28,7 +28,7 @@ namespace { [[nodiscard]] std::list synthesizeButtonKey( InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) { std::list out; if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) && @@ -88,7 +88,7 @@ bool isPointerDown(int32_t buttonState) { [[nodiscard]] std::list synthesizeButtonKeys( InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) { std::list out; out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId, diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h index 3023e686c2..13d952b99c 100644 --- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h +++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h @@ -36,7 +36,7 @@ bool isPointerDown(int32_t buttonState); [[nodiscard]] std::list synthesizeButtonKeys( InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime, - int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags, + int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState); // For devices connected over Bluetooth, although they may produce events at a consistent rate, diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index cf07506d52..489bea8959 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -555,8 +555,8 @@ std::optional TouchInputMapper::findViewport() { if (viewport) { return viewport; } else { - ALOGW("Can't find designated display viewport with ID %" PRId32 " for pointers.", - mConfig.defaultPointerDisplayId); + ALOGW("Can't find designated display viewport with ID %s for pointers.", + mConfig.defaultPointerDisplayId.toString().c_str()); } } @@ -1043,10 +1043,10 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded) if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) { ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %s, mode %s, " - "display id %d", + "display id %s", getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(), ftl::enum_string(mInputDeviceOrientation).c_str(), - ftl::enum_string(mDeviceMode).c_str(), mViewport.displayId); + ftl::enum_string(mDeviceMode).c_str(), mViewport.displayId.toString().c_str()); configureVirtualKeys(); @@ -2646,7 +2646,7 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns PointerCoords pointerCoords; pointerCoords.clear(); out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, ADISPLAY_ID_NONE, policyFlags, + mSource, ui::ADISPLAY_ID_NONE, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, @@ -3477,7 +3477,7 @@ std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs hovering = false; } - return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, ADISPLAY_ID_NONE); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, ui::ADISPLAY_ID_NONE); } std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, @@ -3491,7 +3491,8 @@ std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t std::list TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, - bool hovering, int32_t displayId) { + bool hovering, + ui::LogicalDisplayId displayId) { LOG_ALWAYS_FATAL_IF(mDeviceMode != DeviceMode::POINTER, "%s cannot be used when the device is not in POINTER mode.", __func__); std::list out; @@ -3683,14 +3684,14 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( source |= AINPUT_SOURCE_BLUETOOTH_STYLUS; } - const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE); + const ui::LogicalDisplayId displayId = getAssociatedDisplayId().value_or(ui::ADISPLAY_ID_NONE); float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; if (mDeviceMode == DeviceMode::POINTER) { xCursorPosition = yCursorPosition = 0.f; } - const int32_t deviceId = getDeviceId(); + const DeviceId deviceId = getDeviceId(); std::vector frames = getDeviceContext().getVideoFrames(); std::for_each(frames.begin(), frames.end(), [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); }); @@ -3957,10 +3958,10 @@ bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, return true; } -std::optional TouchInputMapper::getAssociatedDisplayId() { +std::optional TouchInputMapper::getAssociatedDisplayId() { if (mParameters.hasAssociatedDisplay) { if (mDeviceMode == DeviceMode::POINTER) { - return ADISPLAY_ID_NONE; + return ui::ADISPLAY_ID_NONE; } else { return std::make_optional(mViewport.displayId); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 6485ab2159..0c9ffa24d0 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -26,13 +26,12 @@ #include #include -#include -#include #include #include #include #include #include +#include #include #include #include @@ -186,7 +185,7 @@ public: [[nodiscard]] std::list timeoutExpired(nsecs_t when) override; [[nodiscard]] std::list updateExternalStylusState( const StylusState& state) override; - std::optional getAssociatedDisplayId() override; + std::optional getAssociatedDisplayId() override; protected: CursorButtonAccumulator mCursorButtonAccumulator; @@ -706,7 +705,7 @@ private: // Values reported for the last pointer event. uint32_t source; - int32_t displayId; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; float lastCursorX; float lastCursorY; @@ -719,7 +718,7 @@ private: hovering = false; downTime = 0; source = 0; - displayId = ADISPLAY_ID_NONE; + displayId = ui::ADISPLAY_ID_NONE; lastCursorX = 0.f; lastCursorY = 0.f; } @@ -810,7 +809,8 @@ private: [[nodiscard]] std::list dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool down, - bool hovering, int32_t displayId); + bool hovering, + ui::LogicalDisplayId displayId); [[nodiscard]] std::list abortPointerSimple(nsecs_t when, nsecs_t readTime, uint32_t policyFlags); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index 6a38aa7b08..e157862f85 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -309,7 +309,8 @@ void TouchpadInputMapper::dump(std::string& dump) { } dump += INDENT3 "Captured event converter:\n"; dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4); - dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str()); + dump += StringPrintf(INDENT3 "DisplayId: %s\n", + toString(mDisplayId, streamableToString).c_str()); } std::list TouchpadInputMapper::reconfigure(nsecs_t when, @@ -321,7 +322,7 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, } if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { - mDisplayId = ADISPLAY_ID_NONE; + mDisplayId = ui::ADISPLAY_ID_NONE; std::optional resolvedViewport; std::optional boundsInLogicalDisplay; if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) { @@ -334,7 +335,7 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, // Always use DISPLAY_ID_NONE for touchpad events. // PointerChoreographer will make it target the correct the displayId later. resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); - mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt; + mDisplayId = resolvedViewport ? std::make_optional(ui::ADISPLAY_ID_NONE) : std::nullopt; } mGestureConverter.setDisplayId(mDisplayId); @@ -497,7 +498,7 @@ std::list TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t return out; } -std::optional TouchpadInputMapper::getAssociatedDisplayId() { +std::optional TouchpadInputMapper::getAssociatedDisplayId() { return mDisplayId; } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 9f685ec6a8..f27895fd9c 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -66,7 +66,7 @@ public: using MetricsIdentifier = std::tuple; - std::optional getAssociatedDisplayId() override; + std::optional getAssociatedDisplayId() override; private: void resetGestureInterpreter(nsecs_t when); @@ -109,7 +109,7 @@ private: // The display that events generated by this mapper should target. This can be set to // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. - std::optional mDisplayId; + std::optional mDisplayId; nsecs_t mGestureStartTime{0}; }; diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h index e6ced0f02c..829fb9289b 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h @@ -51,7 +51,7 @@ public: void setOrientation(ui::Rotation orientation) { mOrientation = orientation; } [[nodiscard]] std::list reset(nsecs_t when); - void setDisplayId(std::optional displayId) { mDisplayId = displayId; } + void setDisplayId(std::optional displayId) { mDisplayId = displayId; } void setBoundsInLogicalDisplay(FloatRect bounds) { mBoundsInLogicalDisplay = bounds; } @@ -100,7 +100,7 @@ private: InputReaderContext& mReaderContext; const bool mEnableFlingStop; - std::optional mDisplayId; + std::optional mDisplayId; FloatRect mBoundsInLogicalDisplay{}; ui::Rotation mOrientation = ui::ROTATION_0; RawAbsoluteAxisInfo mXAxisInfo; diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index c237fb68aa..72b5573047 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -50,8 +50,8 @@ constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS; constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION; -constexpr int32_t DISPLAY_ID = 0; -constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1}; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; @@ -909,7 +909,7 @@ TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) { EXPECT_THAT(args, ElementsAre(VariantWith( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE), + WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ui::ADISPLAY_ID_NONE), WithCoords(0.0f, 0.0f))))); } diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp index 36491ab64f..530416c7aa 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp @@ -396,8 +396,8 @@ void FakeInputDispatcherPolicy::interceptKeyBeforeQueueing(const KeyEvent& input } } -void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, - uint32_t&) {} +void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t, + int32_t, nsecs_t, uint32_t&) {} nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp&, const KeyEvent&, uint32_t) { @@ -426,7 +426,7 @@ void FakeInputDispatcherPolicy::notifySwitch(nsecs_t when, uint32_t switchValues } void FakeInputDispatcherPolicy::pokeUserActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId) { + ui::LogicalDisplayId displayId) { std::scoped_lock lock(mLock); mNotifyUserActivity.notify_all(); mUserActivityPokeEvents.push({eventTime, eventType, displayId}); diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index 25d3d3c7ed..2c86146ba3 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -53,7 +53,7 @@ public: struct UserActivityPokeEvent { nsecs_t eventTime; int32_t eventType; - int32_t displayId; + ui::LogicalDisplayId displayId; bool operator==(const UserActivityPokeEvent& rhs) const = default; inline friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) { @@ -183,13 +183,15 @@ private: void notifyVibratorState(int32_t deviceId, bool isOn) override; bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override; void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override; - void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override; + void interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t, int32_t, nsecs_t, + uint32_t&) override; nsecs_t interceptKeyBeforeDispatching(const sp&, const KeyEvent&, uint32_t) override; std::optional dispatchUnhandledKey(const sp&, const KeyEvent& event, uint32_t) override; void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) override; - void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) override; bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override; void onPointerDownOutsideFocus(const sp& newToken) override; void setPointerCapture(const PointerCaptureRequest& request) override; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index e2dcb41ac0..088c7df046 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -82,9 +82,9 @@ void FakeInputReaderPolicy::addDisplayViewport(DisplayViewport viewport) { mConfig.setDisplayViewports(mViewports); } -void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height, - ui::Rotation orientation, bool isActive, - const std::string& uniqueId, +void FakeInputReaderPolicy::addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width, + int32_t height, ui::Rotation orientation, + bool isActive, const std::string& uniqueId, std::optional physicalPort, ViewportType type) { const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270; @@ -178,7 +178,7 @@ PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp return mConfig.pointerCaptureRequest; } -void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) { +void FakeInputReaderPolicy::setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId) { mConfig.defaultPointerDisplayId = pointerDisplayId; } @@ -255,8 +255,8 @@ void FakeInputReaderPolicy::notifyStylusGestureStarted(int32_t deviceId, nsecs_t } std::optional FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) { - if (associatedDisplayId == ADISPLAY_ID_NONE) { + ui::LogicalDisplayId associatedDisplayId) { + if (!associatedDisplayId.isValid()) { associatedDisplayId = mConfig.defaultPointerDisplayId; } for (auto& viewport : mViewports) { diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h index 88f0ba7f9b..94f1311a1e 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.h +++ b/services/inputflinger/tests/FakeInputReaderPolicy.h @@ -48,7 +48,7 @@ public: std::optional getDisplayViewportByType(ViewportType type) const; std::optional getDisplayViewportByPort(uint8_t displayPort) const; void addDisplayViewport(DisplayViewport viewport); - void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, + void addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height, ui::Rotation orientation, bool isActive, const std::string& uniqueId, std::optional physicalPort, ViewportType type); bool updateViewport(const DisplayViewport& viewport); @@ -67,7 +67,7 @@ public: ui::Rotation surfaceRotation); void setTouchAffineTransformation(const TouchAffineTransformation t); PointerCaptureRequest setPointerCapture(const sp& window); - void setDefaultPointerDisplayId(int32_t pointerDisplayId); + void setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId); void setPointerGestureEnabled(bool enabled); float getPointerGestureMovementSpeedRatio(); float getPointerGestureZoomSpeedRatio(); @@ -77,7 +77,7 @@ public: void setIsInputMethodConnectionActive(bool active); bool isInputMethodConnectionActive() override; std::optional getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) override; + ui::LogicalDisplayId associatedDisplayId) override; private: void getReaderConfiguration(InputReaderConfiguration* outConfig) override; diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 28d4b6751d..731a28668f 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -32,7 +32,7 @@ void FakePointerController::clearBounds() { mHaveBounds = false; } -const std::map>& FakePointerController::getSpots() { +const std::map>& FakePointerController::getSpots() { return mSpotsByDisplay; } @@ -51,9 +51,9 @@ FloatPoint FakePointerController::getPosition() const { return {mX, mY}; } -int32_t FakePointerController::getDisplayId() const { +ui::LogicalDisplayId FakePointerController::getDisplayId() const { if (!mEnabled || !mDisplayId) { - return ADISPLAY_ID_NONE; + return ui::ADISPLAY_ID_NONE; } return *mDisplayId; } @@ -76,7 +76,7 @@ void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCustomIconStyle = icon.style; } -void FakePointerController::setSkipScreenshot(int32_t displayId, bool skip) { +void FakePointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { if (skip) { mDisplaysToSkipScreenshot.insert(displayId); } else { @@ -84,7 +84,7 @@ void FakePointerController::setSkipScreenshot(int32_t displayId, bool skip) { } }; -void FakePointerController::assertViewportSet(int32_t displayId) { +void FakePointerController::assertViewportSet(ui::LogicalDisplayId displayId) { ASSERT_TRUE(mDisplayId); ASSERT_EQ(displayId, mDisplayId); } @@ -99,7 +99,7 @@ void FakePointerController::assertPosition(float x, float y) { ASSERT_NEAR(y, actualY, 1); } -void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) { +void FakePointerController::assertSpotCount(ui::LogicalDisplayId displayId, int32_t count) { auto it = mSpotsByDisplay.find(displayId); ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId; ASSERT_EQ(static_cast(count), it->second.size()); @@ -125,7 +125,8 @@ void FakePointerController::assertCustomPointerIconNotSet() { ASSERT_EQ(std::nullopt, mCustomIconStyle); } -void FakePointerController::assertIsHiddenOnMirroredDisplays(int32_t displayId, bool isHidden) { +void FakePointerController::assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId, + bool isHidden) { if (isHidden) { ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end()); } else { @@ -166,7 +167,7 @@ void FakePointerController::unfade(Transition) { } void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, - int32_t displayId) { + ui::LogicalDisplayId displayId) { if (!mEnabled) return; std::vector newSpots; diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index b5b982e012..5bc713f80d 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -17,7 +17,6 @@ #pragma once #include -#include #include #include #include @@ -38,26 +37,26 @@ public: void setBounds(float minX, float minY, float maxX, float maxY); void clearBounds(); - const std::map>& getSpots(); + const std::map>& getSpots(); void setPosition(float x, float y) override; FloatPoint getPosition() const override; - int32_t getDisplayId() const override; + ui::LogicalDisplayId getDisplayId() const override; void setDisplayViewport(const DisplayViewport& viewport) override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(int32_t displayId, bool skip) override; + void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; void fade(Transition) override; - void assertViewportSet(int32_t displayId); + void assertViewportSet(ui::LogicalDisplayId displayId); void assertViewportNotSet(); void assertPosition(float x, float y); - void assertSpotCount(int32_t displayId, int32_t count); + void assertSpotCount(ui::LogicalDisplayId displayId, int32_t count); void assertPointerIconSet(PointerIconStyle iconId); void assertPointerIconNotSet(); void assertCustomPointerIconSet(PointerIconStyle iconId); void assertCustomPointerIconNotSet(); - void assertIsHiddenOnMirroredDisplays(int32_t displayId, bool isHidden); + void assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId, bool isHidden); bool isPointerShown(); private: @@ -67,20 +66,20 @@ private: void unfade(Transition) override; void setPresentation(Presentation) override {} void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits, - int32_t displayId) override; + ui::LogicalDisplayId displayId) override; void clearSpots() override; const bool mEnabled; bool mHaveBounds{false}; float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0}; float mX{0}, mY{0}; - std::optional mDisplayId; + std::optional mDisplayId; bool mIsPointerShown{false}; std::optional mIconStyle; std::optional mCustomIconStyle; - std::map> mSpotsByDisplay; - std::unordered_set mDisplaysToSkipScreenshot; + std::map> mSpotsByDisplay; + std::unordered_set mDisplaysToSkipScreenshot; }; } // namespace android diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp index a6955eca94..c800d6ad4e 100644 --- a/services/inputflinger/tests/FakeWindows.cpp +++ b/services/inputflinger/tests/FakeWindows.cpp @@ -81,7 +81,7 @@ void FakeInputReceiver::sendTimeline(int32_t inputEventId, } void FakeInputReceiver::consumeEvent(InputEventType expectedEventType, int32_t expectedAction, - std::optional expectedDisplayId, + std::optional expectedDisplayId, std::optional expectedFlags) { std::unique_ptr event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED); @@ -152,7 +152,7 @@ void FakeInputReceiver::consumeFocusEvent(bool hasFocus, bool inTouchMode) { ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "Instead of FocusEvent, got " << *event; - ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; FocusEvent& focusEvent = static_cast(*event); @@ -165,7 +165,7 @@ void FakeInputReceiver::consumeCaptureEvent(bool hasCapture) { ASSERT_EQ(InputEventType::CAPTURE, event->getType()) << "Instead of CaptureEvent, got " << *event; - ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; const auto& captureEvent = static_cast(*event); @@ -177,7 +177,7 @@ void FakeInputReceiver::consumeDragEvent(bool isExiting, float x, float y) { ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event; - EXPECT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + EXPECT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; const auto& dragEvent = static_cast(*event); @@ -192,7 +192,7 @@ void FakeInputReceiver::consumeTouchModeEvent(bool inTouchMode) { ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) << "Instead of TouchModeEvent, got " << *event; - ASSERT_EQ(ADISPLAY_ID_NONE, event->getDisplayId()) + ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; const auto& touchModeEvent = static_cast(*event); EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode()); @@ -244,7 +244,7 @@ std::atomic FakeWindowHandle::sId{1}; FakeWindowHandle::FakeWindowHandle( const std::shared_ptr& inputApplicationHandle, const std::unique_ptr& dispatcher, const std::string name, - int32_t displayId, bool createInputChannel) + ui::LogicalDisplayId displayId, bool createInputChannel) : mName(name) { sp token; if (createInputChannel) { @@ -272,7 +272,7 @@ FakeWindowHandle::FakeWindowHandle( mInfo.inputConfig = InputConfig::DEFAULT; } -sp FakeWindowHandle::clone(int32_t displayId) { +sp FakeWindowHandle::clone(ui::LogicalDisplayId displayId) { sp handle = sp::make(mInfo.name + "(Mirror)"); handle->mInfo = mInfo; handle->mInfo.displayId = displayId; diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index 6cd76b229d..8a40337db4 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -79,7 +79,7 @@ public: void sendTimeline(int32_t inputEventId, std::array timeline); void consumeEvent(android::InputEventType expectedEventType, int32_t expectedAction, - std::optional expectedDisplayId, + std::optional expectedDisplayId, std::optional expectedFlags); std::unique_ptr consumeMotion(); @@ -119,9 +119,10 @@ public: FakeWindowHandle(const std::shared_ptr& inputApplicationHandle, const std::unique_ptr& dispatcher, - const std::string name, int32_t displayId, bool createInputChannel = true); + const std::string name, ui::LogicalDisplayId displayId, + bool createInputChannel = true); - sp clone(int32_t displayId); + sp clone(ui::LogicalDisplayId displayId); inline void setTouchable(bool touchable) { mInfo.setInputConfig(InputConfig::NOT_TOUCHABLE, !touchable); @@ -249,37 +250,39 @@ public: return keyEvent; } - inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + inline void consumeKeyDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } - inline void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + inline void consumeKeyUp(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } - inline void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionCancel( + ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); } - inline void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + inline void consumeMotionMove(ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } - inline void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + inline void consumeMotionDown(ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { consumeAnyMotionDown(expectedDisplayId, expectedFlags); } - inline void consumeAnyMotionDown(std::optional expectedDisplayId = std::nullopt, - std::optional expectedFlags = std::nullopt) { + inline void consumeAnyMotionDown( + std::optional expectedDisplayId = std::nullopt, + std::optional expectedFlags = std::nullopt) { consumeMotionEvent( testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), testing::Conditional(expectedDisplayId.has_value(), @@ -288,9 +291,9 @@ public: WithFlags(*expectedFlags), testing::_))); } - inline void consumeMotionPointerDown(int32_t pointerIdx, - int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionPointerDown( + int32_t pointerIdx, ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); consumeMotionEvent(testing::AllOf(WithMotionAction(action), @@ -298,9 +301,9 @@ public: WithFlags(expectedFlags))); } - inline void consumeMotionPointerUp(int32_t pointerIdx, - int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionPointerUp( + int32_t pointerIdx, ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); consumeMotionEvent(testing::AllOf(WithMotionAction(action), @@ -308,15 +311,16 @@ public: WithFlags(expectedFlags))); } - inline void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, + inline void consumeMotionUp(ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } - inline void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionOutside( + ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp index cb8c3cb03c..f794da5daf 100644 --- a/services/inputflinger/tests/FocusResolver_test.cpp +++ b/services/inputflinger/tests/FocusResolver_test.cpp @@ -74,7 +74,7 @@ TEST(FocusResolverTest, SetFocusedWindow) { std::optional changes = focusResolver.setFocusedWindow(request, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken); - ASSERT_EQ(request.displayId, changes->displayId); + ASSERT_EQ(ui::LogicalDisplayId{request.displayId}, changes->displayId); // invisible window cannot get focused request.token = invisibleWindowToken; @@ -169,19 +169,20 @@ TEST(FocusResolverTest, FocusTransferToMirror) { ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken); // The mirror window now comes on top, and the focus does not change - changes = focusResolver.setInputWindows(request.displayId, {mirror, window}); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, + {mirror, window}); ASSERT_FALSE(changes.has_value()); // The window now comes on top while the mirror is removed, and the focus does not change - changes = focusResolver.setInputWindows(request.displayId, {window}); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {window}); ASSERT_FALSE(changes.has_value()); // The window is removed but the mirror is on top, and focus does not change - changes = focusResolver.setInputWindows(request.displayId, {mirror}); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {mirror}); ASSERT_FALSE(changes.has_value()); // All windows removed - changes = focusResolver.setInputWindows(request.displayId, {}); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {}); ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr); } @@ -203,12 +204,12 @@ TEST(FocusResolverTest, SetInputWindows) { ASSERT_EQ(focusableWindowToken, changes->newFocus); // When there are no changes to the window, focus does not change - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FALSE(changes.has_value()); // Window visibility changes and the window loses focus window->setVisible(false); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr); } @@ -232,7 +233,7 @@ TEST(FocusResolverTest, FocusRequestsCanBePending) { // Window visibility changes and the window gets focused invisibleWindow->setVisible(true); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ invisibleWindowToken); } @@ -256,25 +257,25 @@ TEST(FocusResolverTest, FocusRequestsArePersistent) { // Focusability changes and the window gets focused window->setFocusable(true); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); // Visibility changes and the window loses focus window->setVisible(false); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr); // Visibility changes and the window gets focused window->setVisible(true); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); // Window is gone and the window loses focus - changes = focusResolver.setInputWindows(request.displayId, {}); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {}); ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr); // Window returns and the window gains focus - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); } @@ -307,27 +308,27 @@ TEST(FocusResolverTest, FocusTransferTarget) { // Embedded is now focusable so will gain focus embeddedWindow->setFocusable(true); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); // Embedded is not visible so host will get focus embeddedWindow->setVisible(false); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); // Embedded is now visible so will get focus embeddedWindow->setVisible(true); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken); // Remove focusTransferTarget from host. Host will gain focus. hostWindow->editInfo()->focusTransferTarget = nullptr; - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken); // Set invalid token for focusTransferTarget. Host will remain focus hostWindow->editInfo()->focusTransferTarget = sp::make(); - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FALSE(changes); } @@ -415,16 +416,16 @@ TEST(FocusResolverTest, FocusRequestsAreClearedWhenWindowIsRemoved) { std::optional changes = focusResolver.setFocusedWindow(request, windows); ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken); - ASSERT_EQ(request.displayId, changes->displayId); + ASSERT_EQ(ui::LogicalDisplayId{request.displayId}, changes->displayId); // When a display is removed, all windows are removed from the display // and our focused window loses focus - changes = focusResolver.setInputWindows(request.displayId, {}); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {}); ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr); - focusResolver.displayRemoved(request.displayId); + focusResolver.displayRemoved(ui::LogicalDisplayId{request.displayId}); // When a display is re-added, the window does not get focus since the request was cleared. - changes = focusResolver.setInputWindows(request.displayId, windows); + changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows); ASSERT_FALSE(changes); } diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index f50f5173b8..1132e9284c 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include "FakeEventHub.h" #include "FakeInputReaderPolicy.h" @@ -46,6 +45,7 @@ const auto TOUCHPAD_PALM_REJECTION_V2 = } // namespace +using android::ui::ADISPLAY_ID_DEFAULT; using testing::AllOf; using testing::Each; using testing::ElementsAre; @@ -93,7 +93,7 @@ protected: TEST_F(GestureConverterTest, Move) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -125,7 +125,7 @@ TEST_F(GestureConverterTest, Move_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -147,7 +147,7 @@ TEST_F(GestureConverterTest, Move_Rotated) { TEST_F(GestureConverterTest, ButtonsChange) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); // Press left and right buttons at once Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -172,7 +172,7 @@ TEST_F(GestureConverterTest, ButtonsChange) { ASSERT_THAT(args, Each(VariantWith(AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); // Then release the left button Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -203,13 +203,13 @@ TEST_F(GestureConverterTest, ButtonsChange) { ASSERT_THAT(args, Each(VariantWith(AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -229,7 +229,7 @@ TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { TEST_F(GestureConverterTest, DragWithButton) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); // Press the button Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -248,7 +248,7 @@ TEST_F(GestureConverterTest, DragWithButton) { ASSERT_THAT(args, Each(VariantWith(AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); // Move Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); @@ -258,7 +258,7 @@ TEST_F(GestureConverterTest, DragWithButton) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0), WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); // Release the button Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -276,14 +276,14 @@ TEST_F(GestureConverterTest, DragWithButton) { ASSERT_THAT(args, Each(VariantWith(AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll) { const nsecs_t downTime = 12345; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -303,7 +303,7 @@ TEST_F(GestureConverterTest, Scroll) { AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); @@ -314,7 +314,7 @@ TEST_F(GestureConverterTest, Scroll) { WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(ToolType::FINGER), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); @@ -333,7 +333,7 @@ TEST_F(GestureConverterTest, Scroll) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_Rotated) { @@ -341,7 +341,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -360,7 +360,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { Each(VariantWith( AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); @@ -388,13 +388,13 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -412,13 +412,13 @@ TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { ASSERT_THAT(args, ElementsAre(VariantWith( AllOf(WithMotionClassification(MotionClassification::NONE), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -443,7 +443,7 @@ TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/0); @@ -464,7 +464,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5, /*dy=*/5); @@ -491,7 +491,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { // only checks movement in one dimension. InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); @@ -502,7 +502,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { Each(VariantWith( AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. @@ -548,7 +548,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); @@ -590,21 +590,21 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(4u, args.size()); - ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ADISPLAY_ID_DEFAULT)))); + ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ui::ADISPLAY_ID_DEFAULT)))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. @@ -648,7 +648,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15); @@ -680,7 +680,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 10, /* dy= */ 0); @@ -691,7 +691,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { Each(VariantWith( AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); // Four fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. @@ -746,7 +746,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15); @@ -799,13 +799,13 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_Inwards) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); @@ -825,7 +825,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { AllOf(WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); @@ -837,7 +837,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0), WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); @@ -923,13 +923,13 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); @@ -954,7 +954,7 @@ TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); @@ -981,7 +981,7 @@ TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { TEST_F(GestureConverterTest, ResetWithButtonPressed) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, @@ -1007,13 +1007,13 @@ TEST_F(GestureConverterTest, ResetWithButtonPressed) { ASSERT_THAT(args, Each(VariantWith(AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringScroll) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); @@ -1033,13 +1033,13 @@ TEST_F(GestureConverterTest, ResetDuringScroll) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/10); @@ -1074,13 +1074,13 @@ TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringPinch) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); @@ -1106,13 +1106,13 @@ TEST_F(GestureConverterTest, ResetDuringPinch) { WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, FlingTapDown) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); @@ -1129,7 +1129,7 @@ TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -1161,7 +1161,7 @@ TEST_F(GestureConverterTest, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1201,14 +1201,14 @@ TEST_F(GestureConverterTest, Tap) { Each(VariantWith(AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); } TEST_F(GestureConverterTest, Click) { // Click should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1238,7 +1238,7 @@ TEST_F(GestureConverterTest, Click) { Each(VariantWith(AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, @@ -1273,7 +1273,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1302,7 +1302,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1384,7 +1384,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1414,7 +1414,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, Each(VariantWith(AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, @@ -1452,7 +1452,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -1468,7 +1468,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove, const nsecs_t gestureStartTime = 1000; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); // Start a move gesture at gestureStartTime Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10); diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp index 85e055d98e..029414b23d 100644 --- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp +++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -64,7 +63,7 @@ InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID, uint32_t sources = TOUCHSCREEN | STYLUS) { auto info = InputDeviceInfo(); info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id), - "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE); + "alias", /*isExternal=*/false, /*hasMic=*/false, ui::ADISPLAY_ID_NONE); info.addSource(sources); return info; } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 09b358afe5..184659df0d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -53,6 +53,7 @@ using android::gui::WindowInfo; using android::gui::WindowInfoHandle; using android::os::InputEventInjectionResult; using android::os::InputEventInjectionSync; +using android::ui::ADISPLAY_ID_DEFAULT; namespace android::inputdispatcher { @@ -72,8 +73,8 @@ static constexpr int32_t DEVICE_ID = DEFAULT_DEVICE_ID; static constexpr int32_t SECOND_DEVICE_ID = 2; // An arbitrary display id. -static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; -static constexpr int32_t SECOND_DISPLAY_ID = 1; +constexpr ui::LogicalDisplayId DISPLAY_ID = ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId SECOND_DISPLAY_ID = ui::LogicalDisplayId{1}; // Ensure common actions are interchangeable between keys and motions for convenience. static_assert(AMOTION_EVENT_ACTION_DOWN == AKEY_EVENT_ACTION_DOWN); @@ -128,7 +129,7 @@ using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID; static KeyEvent getTestKeyEvent() { KeyEvent event; - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); return event; @@ -243,7 +244,7 @@ protected: request.token = window->getToken(); request.windowName = window->getName(); request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); - request.displayId = window->getInfo()->displayId; + request.displayId = window->getInfo()->displayId.val(); mDispatcher->setFocusedWindow(request); } }; @@ -252,7 +253,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { KeyEvent event; // Rejects undefined key actions. - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, INVALID_HMAC, /*action=*/-1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); @@ -262,7 +263,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { << "Should reject key events with undefined action."; // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, @@ -438,12 +439,13 @@ static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms; class FakeMonitorReceiver { public: - FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId) + FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, + ui::LogicalDisplayId displayId) : mInputReceiver(*dispatcher.createInputMonitor(displayId, name, MONITOR_PID), name) {} sp getToken() { return mInputReceiver.getToken(); } - void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + void consumeKeyDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { mInputReceiver.consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } @@ -455,22 +457,22 @@ public: void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); } - void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + void consumeMotionDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId, expectedFlags); } - void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + void consumeMotionMove(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE, expectedDisplayId, expectedFlags); } - void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + void consumeMotionUp(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP, expectedDisplayId, expectedFlags); } - void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) { + void consumeMotionCancel(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) { mInputReceiver.consumeMotionEvent( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithDisplayId(expectedDisplayId), @@ -498,7 +500,7 @@ private: static InputEventInjectionResult injectKey( InputDispatcher& dispatcher, int32_t action, int32_t repeatCount, - int32_t displayId = ADISPLAY_ID_NONE, + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE, InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT, std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT, bool allowKeyRepeat = true, std::optional targetUid = {}, @@ -520,30 +522,30 @@ static InputEventInjectionResult injectKey( static void assertInjectedKeyTimesOut(InputDispatcher& dispatcher) { InputEventInjectionResult result = - injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_NONE, + injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ui::ADISPLAY_ID_NONE, InputEventInjectionSync::WAIT_FOR_RESULT, CONSUME_TIMEOUT_NO_EVENT_EXPECTED); if (result != InputEventInjectionResult::TIMED_OUT) { FAIL() << "Injection should have timed out, but got " << ftl::enum_string(result); } } -static InputEventInjectionResult injectKeyDown(InputDispatcher& dispatcher, - int32_t displayId = ADISPLAY_ID_NONE) { +static InputEventInjectionResult injectKeyDown( + InputDispatcher& dispatcher, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId); } // Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without // sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it // has to be woken up to process the repeating key. -static InputEventInjectionResult injectKeyDownNoRepeat(InputDispatcher& dispatcher, - int32_t displayId = ADISPLAY_ID_NONE) { +static InputEventInjectionResult injectKeyDownNoRepeat( + InputDispatcher& dispatcher, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false); } -static InputEventInjectionResult injectKeyUp(InputDispatcher& dispatcher, - int32_t displayId = ADISPLAY_ID_NONE) { +static InputEventInjectionResult injectKeyUp( + InputDispatcher& dispatcher, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId); } @@ -557,7 +559,7 @@ static InputEventInjectionResult injectMotionEvent( } static InputEventInjectionResult injectMotionEvent( - InputDispatcher& dispatcher, int32_t action, int32_t source, int32_t displayId, + InputDispatcher& dispatcher, int32_t action, int32_t source, ui::LogicalDisplayId displayId, const PointF& position = {100, 200}, const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}, @@ -583,18 +585,19 @@ static InputEventInjectionResult injectMotionEvent( } static InputEventInjectionResult injectMotionDown(InputDispatcher& dispatcher, int32_t source, - int32_t displayId, + ui::LogicalDisplayId displayId, const PointF& location = {100, 200}) { return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location); } static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int32_t source, - int32_t displayId, + ui::LogicalDisplayId displayId, const PointF& location = {100, 200}) { return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location); } -static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) { +static NotifyKeyArgs generateKeyArgs(int32_t action, + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, @@ -604,8 +607,8 @@ static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLA return args; } -static NotifyKeyArgs generateSystemShortcutArgs(int32_t action, - int32_t displayId = ADISPLAY_ID_NONE) { +static NotifyKeyArgs generateSystemShortcutArgs( + int32_t action, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, @@ -615,8 +618,8 @@ static NotifyKeyArgs generateSystemShortcutArgs(int32_t action, return args; } -static NotifyKeyArgs generateAssistantKeyArgs(int32_t action, - int32_t displayId = ADISPLAY_ID_NONE) { +static NotifyKeyArgs generateAssistantKeyArgs( + int32_t action, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, @@ -627,7 +630,7 @@ static NotifyKeyArgs generateAssistantKeyArgs(int32_t action, } [[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, - int32_t displayId, + ui::LogicalDisplayId displayId, const std::vector& points) { size_t pointerCount = points.size(); if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) { @@ -664,7 +667,8 @@ static NotifyMotionArgs generateTouchArgs(int32_t action, const std::vectoronWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); // Inject a MotionEvent to an unknown display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::ADISPLAY_ID_NONE)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. @@ -1267,7 +1271,6 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1); wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); @@ -5567,8 +5570,8 @@ TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTarg ASSERT_NE(nullptr, event); EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction()); - // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the - // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y + // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so + // the coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of // SECOND_DISPLAY_ID, the x and y coordinates are 200 EXPECT_EQ(100, event->getX(0)); @@ -5589,7 +5592,7 @@ public: removeAllWindowsAndDisplays(); } - void addDisplayInfo(int displayId, const ui::Transform& transform) { + void addDisplayInfo(ui::LogicalDisplayId displayId, const ui::Transform& transform) { gui::DisplayInfo info; info.displayId = displayId; info.transform = transform; @@ -7278,7 +7281,7 @@ TEST_F(InputDispatcherTest, GeneratedHmac_ChangesWhenFieldsChange) { verifiedEvent.eventTimeNanos += 1; ASSERT_NE(initialHmac, mDispatcher->sign(verifiedEvent)); - verifiedEvent.displayId += 1; + verifiedEvent.displayId = ui::LogicalDisplayId{verifiedEvent.displayId.val() + 1}; ASSERT_NE(initialHmac, mDispatcher->sign(verifiedEvent)); verifiedEvent.action += 1; @@ -7323,7 +7326,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Focused window should receive event. - windowSecond->consumeKeyDown(ADISPLAY_ID_NONE); + windowSecond->consumeKeyDown(ui::ADISPLAY_ID_NONE); windowTop->assertNoEvents(); } @@ -7388,7 +7391,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Focused window should receive event. - windowSecond->consumeKeyDown(ADISPLAY_ID_NONE); + windowSecond->consumeKeyDown(ui::ADISPLAY_ID_NONE); } TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) { @@ -7411,7 +7414,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Event should be dropped. - windowTop->consumeKeyDown(ADISPLAY_ID_NONE); + windowTop->consumeKeyDown(ui::ADISPLAY_ID_NONE); windowSecond->assertNoEvents(); } @@ -8516,13 +8519,13 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); - windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE); + windowInSecondary->consumeKeyDown(ui::ADISPLAY_ID_NONE); // Remove all windows in secondary display. mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0}); // Old focus should receive a cancel event. - windowInSecondary->consumeKeyUp(ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED); + windowInSecondary->consumeKeyUp(ui::ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED); // Test inject a key down, should timeout because of no target window. ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher)); @@ -8567,12 +8570,12 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { // If specific a display, it will dispatch to the focused window of particular display, // or it will dispatch to the focused window of focused display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ui::ADISPLAY_ID_NONE)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); monitorInPrimary.assertNoEvents(); - windowInSecondary->consumeMotionDown(ADISPLAY_ID_NONE); - monitorInSecondary.consumeMotionDown(ADISPLAY_ID_NONE); + windowInSecondary->consumeMotionDown(ui::ADISPLAY_ID_NONE); + monitorInSecondary.consumeMotionDown(ui::ADISPLAY_ID_NONE); } // Test per-display input monitors for key event. @@ -8588,8 +8591,8 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); monitorInPrimary.assertNoEvents(); - windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE); - monitorInSecondary.consumeKeyDown(ADISPLAY_ID_NONE); + windowInSecondary->consumeKeyDown(ui::ADISPLAY_ID_NONE); + monitorInSecondary.consumeKeyDown(ui::ADISPLAY_ID_NONE); } TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) { @@ -8748,7 +8751,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropMotionEvent_OnlyCancelCorr class InputFilterTest : public InputDispatcherTest { protected: - void testNotifyMotion(int32_t displayId, bool expectToBeFiltered, + void testNotifyMotion(ui::LogicalDisplayId displayId, bool expectToBeFiltered, const ui::Transform& transform = ui::Transform()) { NotifyMotionArgs motionArgs; @@ -8873,7 +8876,7 @@ protected: const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); event.initialize(InputEvent::nextId(), injectedDeviceId, AINPUT_SOURCE_KEYBOARD, - ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, + ui::ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT; @@ -8967,7 +8970,7 @@ protected: mWindow->consumeFocusEvent(true); } - void notifyAndConsumeMotion(int32_t action, uint32_t source, int32_t displayId, + void notifyAndConsumeMotion(int32_t action, uint32_t source, ui::LogicalDisplayId displayId, nsecs_t eventTime) { mDispatcher->notifyMotion(MotionArgsBuilder(action, source) .displayId(displayId) @@ -9482,7 +9485,7 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenTouchIsConsumed_NoAnr) { // Send a regular key and respond, which should not cause an ANR. TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher)); - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); } @@ -9558,7 +9561,8 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) { // injection times out (instead of failing). const InputEventInjectionResult result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, /*allowKeyRepeat=*/false); + InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, + /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication); @@ -9581,7 +9585,7 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count(); // Define a valid key down event that is stale (too old). - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE, + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); @@ -10262,7 +10266,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { InputEventInjectionResult result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms); + InputEventInjectionSync::NONE, + /*injectionTimeout=*/100ms); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Key will not be sent to the window, yet, because the window is still processing events // and the key remains pending, waiting for the touch events to be processed. @@ -10365,7 +10370,8 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ // Key will not be sent anywhere because we have no focused window. It will remain pending. InputEventInjectionResult result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + InputEventInjectionSync::NONE, + /*injectionTimeout=*/100ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); @@ -10467,7 +10473,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvent // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is. InputEventInjectionResult result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + InputEventInjectionSync::NONE, + /*injectionTimeout=*/100ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); @@ -10476,7 +10483,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvent .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) .build()); result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + InputEventInjectionSync::NONE, + /*injectionTimeout=*/100ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // The key that was injected is blocking the dispatcher, so the navigation bar shouldn't be @@ -10598,7 +10606,7 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, CanGetFocus) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); } // A focused & mirrored window remains focused only if the window and its mirror are both @@ -10610,10 +10618,10 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAllWindowsFocusable) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); mMirror->setFocusable(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); @@ -10635,20 +10643,20 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAnyWindowVisible) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); mMirror->setVisible(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); mWindow->setVisible(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); @@ -10669,20 +10677,20 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedWhileWindowsAlive) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); // single window is removed but the window token remains focused mDispatcher->onWindowInfosChanged({{*mMirror->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mMirror->consumeKeyDown(ADISPLAY_ID_NONE); + mMirror->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mMirror->consumeKeyUp(ADISPLAY_ID_NONE); + mMirror->consumeKeyUp(ui::ADISPLAY_ID_NONE); // Both windows are removed mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); @@ -12489,11 +12497,11 @@ TEST_F(InputDispatcherSpyWindowTest, UnfocusableSpyDoesNotReceiveKeyEvents) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeKeyDown(ADISPLAY_ID_NONE); + window->consumeKeyDown(ui::ADISPLAY_ID_NONE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeKeyUp(ADISPLAY_ID_NONE); + window->consumeKeyUp(ui::ADISPLAY_ID_NONE); spy->assertNoEvents(); } @@ -13073,7 +13081,8 @@ struct User { } InputEventInjectionResult injectTargetedKey(int32_t action) const { - return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE, + return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0, + ui::ADISPLAY_ID_NONE, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid}, mPolicyFlags); @@ -13105,7 +13114,7 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) { EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, owner.injectTargetedKey(AKEY_EVENT_ACTION_DOWN)); - window->consumeKeyDown(ADISPLAY_ID_NONE); + window->consumeKeyDown(ui::ADISPLAY_ID_NONE); } TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) { @@ -13178,7 +13187,7 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTarget randosSpy->consumeFocusEvent(true); EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)); - randosSpy->consumeKeyDown(ADISPLAY_ID_NONE); + randosSpy->consumeKeyDown(ui::ADISPLAY_ID_NONE); window->assertNoEvents(); } diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index ae6c849f14..fea1349b88 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -172,8 +172,8 @@ std::shared_ptr InputMapperTest::newDevice(int32_t deviceId, const return device; } -void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, - ui::Rotation orientation, +void InputMapperTest::setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, + int32_t height, ui::Rotation orientation, const std::string& uniqueId, std::optional physicalPort, ViewportType viewportType) { diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 509a5937a7..5bd8cda976 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -128,7 +128,7 @@ protected: args...); } - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height, ui::Rotation orientation, const std::string& uniqueId, std::optional physicalPort, ViewportType viewportType); diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp index 4b42f4b141..15565326b1 100644 --- a/services/inputflinger/tests/InputProcessorConverter_test.cpp +++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp @@ -17,7 +17,6 @@ #include "../InputCommonConverter.h" #include -#include #include using namespace aidl::android::hardware::input; @@ -39,7 +38,7 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5); static constexpr nsecs_t downTime = 2; NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, - /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp index 3b7cbfac5c..5606a91a0b 100644 --- a/services/inputflinger/tests/InputProcessor_test.cpp +++ b/services/inputflinger/tests/InputProcessor_test.cpp @@ -16,7 +16,6 @@ #include "../InputProcessor.h" #include -#include #include "TestInputListener.h" @@ -45,7 +44,7 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1); static constexpr nsecs_t downTime = 2; NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, - /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, @@ -81,7 +80,7 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) { TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) { // Create a basic key event and send to processor NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3, - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, + AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, AMETA_NONE, /*downTime=*/6); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 92489aec8d..fcc52a8f31 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include @@ -60,13 +59,14 @@ using std::chrono_literals::operator""ms; using std::chrono_literals::operator""s; // Arbitrary display properties. -static constexpr int32_t DISPLAY_ID = 0; +static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; static const std::string DISPLAY_UNIQUE_ID = "local:1"; -static constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1; +static constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = + ui::LogicalDisplayId{DISPLAY_ID.val() + 1}; static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2"; static constexpr int32_t DISPLAY_WIDTH = 480; static constexpr int32_t DISPLAY_HEIGHT = 800; -static constexpr int32_t VIRTUAL_DISPLAY_ID = 1; +static constexpr ui::LogicalDisplayId VIRTUAL_DISPLAY_ID = ui::LogicalDisplayId{1}; static constexpr int32_t VIRTUAL_DISPLAY_WIDTH = 400; static constexpr int32_t VIRTUAL_DISPLAY_HEIGHT = 500; static const char* VIRTUAL_DISPLAY_UNIQUE_ID = "virtual:1"; @@ -358,7 +358,7 @@ private: virtual void fadePointer() { } - virtual std::optional getAssociatedDisplay() { + virtual std::optional getAssociatedDisplay() { if (mViewport) { return std::make_optional(mViewport->displayId); } @@ -417,8 +417,8 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByType) { const std::string externalUniqueId = "local:1"; const std::string virtualUniqueId1 = "virtual:2"; const std::string virtualUniqueId2 = "virtual:3"; - constexpr int32_t virtualDisplayId1 = 2; - constexpr int32_t virtualDisplayId2 = 3; + constexpr ui::LogicalDisplayId virtualDisplayId1 = ui::LogicalDisplayId{2}; + constexpr ui::LogicalDisplayId virtualDisplayId2 = ui::LogicalDisplayId{3}; // Add an internal viewport mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, @@ -475,8 +475,8 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByType) { TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) { const std::string uniqueId1 = "uniqueId1"; const std::string uniqueId2 = "uniqueId2"; - constexpr int32_t displayId1 = 2; - constexpr int32_t displayId2 = 3; + constexpr ui::LogicalDisplayId displayId1 = ui::LogicalDisplayId{2}; + constexpr ui::LogicalDisplayId displayId2 = ui::LogicalDisplayId{3}; std::vector types = {ViewportType::INTERNAL, ViewportType::EXTERNAL, ViewportType::VIRTUAL}; @@ -520,13 +520,13 @@ TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) { TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { const std::string uniqueId1 = "uniqueId1"; const std::string uniqueId2 = "uniqueId2"; - constexpr int32_t nonDefaultDisplayId = 2; - static_assert(nonDefaultDisplayId != ADISPLAY_ID_DEFAULT, - "Test display ID should not be ADISPLAY_ID_DEFAULT"); + constexpr ui::LogicalDisplayId nonDefaultDisplayId = ui::LogicalDisplayId{2}; + ASSERT_NE(nonDefaultDisplayId, ui::ADISPLAY_ID_DEFAULT) + << "Test display ID should not be ui::ADISPLAY_ID_DEFAULT "; // Add the default display first and ensure it gets returned. mFakePolicy->clearViewports(); - mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, + mFakePolicy->addDisplayViewport(ui::ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, @@ -536,7 +536,7 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { std::optional viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); ASSERT_TRUE(viewport); - ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId); + ASSERT_EQ(ui::ADISPLAY_ID_DEFAULT, viewport->displayId); ASSERT_EQ(ViewportType::INTERNAL, viewport->type); // Add the default display second to make sure order doesn't matter. @@ -544,13 +544,13 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT, ViewportType::INTERNAL); - mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, + mFakePolicy->addDisplayViewport(ui::ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, ViewportType::INTERNAL); viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); ASSERT_TRUE(viewport); - ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId); + ASSERT_EQ(ui::ADISPLAY_ID_DEFAULT, viewport->displayId); ASSERT_EQ(ViewportType::INTERNAL, viewport->type); } @@ -561,8 +561,8 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByPort) { constexpr ViewportType type = ViewportType::EXTERNAL; const std::string uniqueId1 = "uniqueId1"; const std::string uniqueId2 = "uniqueId2"; - constexpr int32_t displayId1 = 1; - constexpr int32_t displayId2 = 2; + constexpr ui::LogicalDisplayId displayId1 = ui::LogicalDisplayId{1}; + constexpr ui::LogicalDisplayId displayId2 = ui::LogicalDisplayId{2}; const uint8_t hdmi1 = 0; const uint8_t hdmi2 = 1; const uint8_t hdmi3 = 2; @@ -1598,7 +1598,7 @@ protected: mDeviceInfo = *info; } - void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height, + void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height, ui::Rotation orientation, const std::string& uniqueId, std::optional physicalPort, ViewportType viewportType) { @@ -3244,7 +3244,7 @@ protected: void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode, - int32_t displayId = ADISPLAY_ID_NONE); + ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE); }; /* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the @@ -3257,7 +3257,8 @@ void KeyboardInputMapperTest::prepareDisplay(ui::Rotation orientation) { void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, int32_t originalKeyCode, - int32_t rotatedKeyCode, int32_t displayId) { + int32_t rotatedKeyCode, + ui::LogicalDisplayId displayId) { NotifyKeyArgs args; process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1); @@ -3583,14 +3584,14 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId); + ASSERT_EQ(ui::ADISPLAY_ID_NONE, args.displayId); prepareDisplay(ui::ROTATION_0); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId); + ASSERT_EQ(ui::ADISPLAY_ID_NONE, args.displayId); } TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { @@ -3615,7 +3616,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); ASSERT_EQ(DISPLAY_ID, args.displayId); - constexpr int32_t newDisplayId = 2; + constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2}; clearViewports(); setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL); @@ -3830,7 +3831,7 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { ASSERT_FALSE(device2->isEnabled()); // Prepare second display. - constexpr int32_t newDisplayId = 2; + constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2}; setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, UNIQUE_ID, hdmi1, ViewportType::INTERNAL); setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, @@ -8674,7 +8675,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(ADISPLAY_ID_NONE, motionArgs.displayId); + ASSERT_EQ(ui::ADISPLAY_ID_NONE, motionArgs.displayId); } /** @@ -9571,7 +9572,7 @@ TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { processPosition(mapper, 100, 100); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ADISPLAY_ID_DEFAULT, motionArgs.displayId); + ASSERT_EQ(ui::ADISPLAY_ID_DEFAULT, motionArgs.displayId); // Expect the event to be sent to the external viewport if it is present. prepareSecondaryDisplay(ViewportType::EXTERNAL); diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp index 0404d6d4ed..dfbbce3b1a 100644 --- a/services/inputflinger/tests/InputTracingTest.cpp +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -41,7 +41,7 @@ using perfetto::protos::pbzero::AndroidInputEventConfig; namespace { -constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; // Ensure common actions are interchangeable between keys and motions for convenience. static_assert(static_cast(AMOTION_EVENT_ACTION_DOWN) == @@ -127,7 +127,7 @@ protected: request.token = window->getToken(); request.windowName = window->getName(); request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); - request.displayId = window->getInfo()->displayId; + request.displayId = window->getInfo()->displayId.val(); mDispatcher->setFocusedWindow(request); } diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp index 6606de896b..01fd03e874 100644 --- a/services/inputflinger/tests/LatencyTracker_test.cpp +++ b/services/inputflinger/tests/LatencyTracker_test.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -44,7 +43,7 @@ static InputDeviceInfo generateTestDeviceInfo(uint16_t vendorId, uint16_t produc identifier.product = productId; auto info = InputDeviceInfo(); info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "Test Device", - /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE); + /*isExternal=*/false, /*hasMic=*/false, ui::ADISPLAY_ID_NONE); return info; } diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp index d726385240..437020f48a 100644 --- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -35,7 +35,7 @@ using testing::Return; using testing::SetArgPointee; using testing::VariantWith; -static constexpr int32_t DISPLAY_ID = 0; +static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; static constexpr int32_t DISPLAY_WIDTH = 480; static constexpr int32_t DISPLAY_HEIGHT = 800; static constexpr std::optional NO_PORT = std::nullopt; // no physical port is specified diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp index 15367568ab..2e5ecc3350 100644 --- a/services/inputflinger/tests/NotifyArgs_test.cpp +++ b/services/inputflinger/tests/NotifyArgs_test.cpp @@ -36,7 +36,7 @@ TEST(NotifyMotionArgsTest, TestCopyAssignmentOperator) { nsecs_t readTime = downTime++; int32_t deviceId = 7; uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; - int32_t displayId = 42; + ui::LogicalDisplayId displayId = ui::LogicalDisplayId{42}; uint32_t policyFlags = POLICY_FLAG_GESTURE; int32_t action = AMOTION_EVENT_ACTION_HOVER_MOVE; int32_t actionButton = AMOTION_EVENT_BUTTON_PRIMARY; diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index d81f8a0280..33e7277034 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -46,8 +46,8 @@ Visitor(V...) -> Visitor; constexpr int32_t DEVICE_ID = 3; constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1; constexpr int32_t THIRD_DEVICE_ID = SECOND_DEVICE_ID + 1; -constexpr int32_t DISPLAY_ID = 5; -constexpr int32_t ANOTHER_DISPLAY_ID = 10; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId{5}; +constexpr ui::LogicalDisplayId ANOTHER_DISPLAY_ID = ui::LogicalDisplayId{10}; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; constexpr auto DRAWING_TABLET_SOURCE = AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS; @@ -63,7 +63,7 @@ const auto TOUCHPAD_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER) .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20); static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source, - int32_t associatedDisplayId) { + ui::LogicalDisplayId associatedDisplayId) { InputDeviceIdentifier identifier; auto info = InputDeviceInfo(); @@ -73,7 +73,7 @@ static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source, return info; } -static std::vector createViewports(std::vector displayIds) { +static std::vector createViewports(std::vector displayIds) { std::vector viewports; for (auto displayId : displayIds) { DisplayViewport viewport; @@ -124,7 +124,7 @@ protected: "reference to this PointerController"; } - void assertPointerDisplayIdNotified(int32_t displayId) { + void assertPointerDisplayIdNotified(ui::LogicalDisplayId displayId) { ASSERT_EQ(displayId, mPointerDisplayIdNotified); mPointerDisplayIdNotified.reset(); } @@ -134,7 +134,7 @@ protected: private: std::deque>> mCreatedControllers; - std::optional mPointerDisplayIdNotified; + std::optional mPointerDisplayIdNotified; std::shared_ptr createPointerController( ControllerType type) override { @@ -144,7 +144,8 @@ private: return pc; } - void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override { + void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, + const FloatPoint& position) override { mPointerDisplayIdNotified = displayId; } }; @@ -201,13 +202,15 @@ TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) { TEST_F(PointerChoreographerTest, WhenMouseIsAddedCreatesPointerController) { mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); assertPointerControllerCreated(ControllerType::MOUSE); } TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) { mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); // Remove the mouse. @@ -218,7 +221,7 @@ TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) { TEST_F(PointerChoreographerTest, WhenKeyboardIsAddedDoesNotCreatePointerController) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE)}}); assertPointerControllerNotCreated(); } @@ -252,7 +255,8 @@ TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) { // For a mouse event without a target display, default viewport should be set for // the PointerController. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertViewportSet(DISPLAY_ID); ASSERT_TRUE(pc->isPointerShown()); @@ -264,7 +268,8 @@ TEST_F(PointerChoreographerTest, mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); firstDisplayPc->assertViewportSet(DISPLAY_ID); ASSERT_TRUE(firstDisplayPc->isPointerShown()); @@ -283,7 +288,8 @@ TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); @@ -292,7 +298,8 @@ TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) { TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterCallsNotifyPointerDisplayIdChanged) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotNotified(); @@ -304,12 +311,13 @@ TEST_F(PointerChoreographerTest, WhenMouseIsRemovedCallsNotifyPointerDisplayIdCh mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); - assertPointerDisplayIdNotified(ADISPLAY_ID_NONE); + assertPointerDisplayIdNotified(ui::ADISPLAY_ID_NONE); assertPointerControllerRemoved(pc); } @@ -320,7 +328,8 @@ TEST_F(PointerChoreographerTest, WhenDefaultMouseDisplayChangesCallsNotifyPointe // Set one viewport as a default mouse display ID. mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); @@ -336,7 +345,8 @@ TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -348,7 +358,7 @@ TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) { MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(MOUSE_POINTER) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); // Check that the PointerController updated the position and the pointer is shown. @@ -364,7 +374,8 @@ TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -379,7 +390,7 @@ TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) { MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(absoluteMousePointer) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); // Check that the PointerController updated the position and the pointer is shown. @@ -401,7 +412,7 @@ TEST_F(PointerChoreographerTest, // Add two devices, one unassociated and the other associated with non-default mouse display. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId()); @@ -437,7 +448,8 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -447,7 +459,8 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { // Assume that pointer capture is enabled. mChoreographer.notifyInputDevicesChanged( {/*id=*/1, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, + ui::ADISPLAY_ID_NONE)}}); mChoreographer.notifyPointerCaptureChanged( NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC), PointerCaptureRequest(/*window=*/sp::make(), @@ -462,7 +475,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); // Check that there's no update on the PointerController. @@ -471,7 +484,8 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { // Check x-y coordinates, displayId and cursor position are not changed. mTestListener.assertNotifyMotionWasCalled( - AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE), + AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), + WithDisplayId(ui::ADISPLAY_ID_NONE), WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION))); } @@ -480,7 +494,8 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); ASSERT_TRUE(pc->isPointerShown()); @@ -499,7 +514,8 @@ TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) { // A mouse is connected, and the pointer is shown. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_TRUE(pc->isPointerShown()); @@ -509,15 +525,17 @@ TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) { // Add a second mouse is added, the pointer is shown again. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::ADISPLAY_ID_NONE)}}); ASSERT_TRUE(pc->isPointerShown()); // One of the mice is removed, and it does not cause the mouse pointer to fade, because // we have one more mouse connected. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::ADISPLAY_ID_NONE)}}); assertPointerControllerNotRemoved(pc); ASSERT_TRUE(pc->isPointerShown()); @@ -530,7 +548,8 @@ TEST_F(PointerChoreographerTest, UnrelatedChangeDoesNotUnfadePointer) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_TRUE(pc->isPointerShown()); @@ -540,7 +559,7 @@ TEST_F(PointerChoreographerTest, UnrelatedChangeDoesNotUnfadePointer) { // Adding a touchscreen device does not unfade the mouse pointer. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); @@ -557,7 +576,7 @@ TEST_F(PointerChoreographerTest, DisabledMouseConnected) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); InputDeviceInfo mouseDeviceInfo = - generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE); + generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); // Disable this mouse device. mouseDeviceInfo.setEnabled(false); mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}}); @@ -570,7 +589,7 @@ TEST_F(PointerChoreographerTest, MouseDeviceDisableLater) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); InputDeviceInfo mouseDeviceInfo = - generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE); + generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}}); @@ -589,14 +608,14 @@ TEST_F(PointerChoreographerTest, MultipleEnabledAndDisabledMiceConnectionAndRemo mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); InputDeviceInfo disabledMouseDeviceInfo = - generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE); + generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); disabledMouseDeviceInfo.setEnabled(false); InputDeviceInfo enabledMouseDeviceInfo = - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE); + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); InputDeviceInfo anotherEnabledMouseDeviceInfo = - generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE); + generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, @@ -1158,7 +1177,7 @@ TEST_F(PointerChoreographerTest, WhenTouchpadIsAddedCreatesPointerController) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); assertPointerControllerCreated(ControllerType::MOUSE); } @@ -1166,7 +1185,7 @@ TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedRemovesPointerController) mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); // Remove the touchpad. @@ -1207,7 +1226,8 @@ TEST_F(PointerChoreographerTest, SetsDefaultTouchpadViewportForPointerController // For a touchpad event without a target display, default viewport should be set for // the PointerController. mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertViewportSet(DISPLAY_ID); } @@ -1220,7 +1240,7 @@ TEST_F(PointerChoreographerTest, mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); firstDisplayPc->assertViewportSet(DISPLAY_ID); @@ -1238,7 +1258,7 @@ TEST_F(PointerChoreographerTest, TouchpadCallsNotifyPointerDisplayIdChanged) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); @@ -1249,7 +1269,7 @@ TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterTouchpadCallsNotifyPointe mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotNotified(); @@ -1263,12 +1283,12 @@ TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedCallsNotifyPointerDisplayI mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); - assertPointerDisplayIdNotified(ADISPLAY_ID_NONE); + assertPointerDisplayIdNotified(ui::ADISPLAY_ID_NONE); assertPointerControllerRemoved(pc); } @@ -1282,11 +1302,11 @@ TEST_F(PointerChoreographerTest, mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); - // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified + // Set another viewport as a default mouse display ID. ui::ADISPLAY_ID_NONE will be notified // before a touchpad event. mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID); assertPointerControllerRemoved(firstDisplayPc); @@ -1301,7 +1321,7 @@ TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -1313,7 +1333,7 @@ TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) { MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(TOUCHPAD_POINTER) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); // Check that the PointerController updated the position and the pointer is shown. @@ -1331,7 +1351,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -1345,7 +1365,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), @@ -1359,7 +1379,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | @@ -1376,7 +1396,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | @@ -1391,7 +1411,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), @@ -1410,7 +1430,7 @@ TEST_F(PointerChoreographerTest, mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE), + ui::ADISPLAY_ID_NONE), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ANOTHER_DISPLAY_ID)}}); auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE); @@ -1449,7 +1469,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -1466,7 +1486,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD) .pointer(FIRST_TOUCH_POINTER) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); // Check that there's no update on the PointerController. @@ -1475,7 +1495,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { // Check x-y coordinates, displayId and cursor position are not changed. mTestListener.assertNotifyMotionWasCalled( - AllOf(WithCoords(100, 200), WithDisplayId(ADISPLAY_ID_NONE), + AllOf(WithCoords(100, 200), WithDisplayId(ui::ADISPLAY_ID_NONE), WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION))); } @@ -1486,7 +1506,7 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledTouchpadHidesPointer) mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); ASSERT_TRUE(pc->isPointerShown()); @@ -1504,7 +1524,8 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouse) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertPointerIconNotSet(); @@ -1518,7 +1539,8 @@ TEST_F(PointerChoreographerTest, DoesNotSetMousePointerIconForWrongDisplayId) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertPointerIconNotSet(); @@ -1533,7 +1555,8 @@ TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForWrongDeviceId) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertPointerIconNotSet(); @@ -1548,7 +1571,8 @@ TEST_F(PointerChoreographerTest, SetsCustomPointerIconForMouse) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertCustomPointerIconNotSet(); @@ -1571,7 +1595,7 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId()); @@ -1766,14 +1790,14 @@ TEST_P(StylusTestFixture, SetsPointerIconForMouseAndStylus) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyMotion( MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(MOUSE_POINTER) .deviceId(DEVICE_ID) - .displayId(ADISPLAY_ID_NONE) + .displayId(ui::ADISPLAY_ID_NONE) .build()); auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE); mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) @@ -1801,7 +1825,7 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerOnDisplay) mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId()); @@ -1857,7 +1881,8 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerWhenDeviceC // Hide the pointer on the display, and then connect the mouse. mChoreographer.setPointerIconVisibility(DISPLAY_ID, false); mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, mousePc->getDisplayId()); @@ -1875,7 +1900,7 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForTouchpad mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ADISPLAY_ID_NONE)}}); + ui::ADISPLAY_ID_NONE)}}); auto touchpadPc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, touchpadPc->getDisplayId()); @@ -1920,7 +1945,7 @@ TEST_F(PointerChoreographerTest, DrawingTabletCanReportMouseEvent) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); // There should be no controller created when a drawing tablet is connected assertPointerControllerNotCreated(); @@ -1947,7 +1972,7 @@ TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { // First drawing tablet is added mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); assertPointerControllerNotCreated(); mChoreographer.notifyMotion( @@ -1962,8 +1987,9 @@ TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { // Second drawing tablet is added mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE), - generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::ADISPLAY_ID_NONE)}}); assertPointerControllerNotRemoved(pc); mChoreographer.notifyMotion( @@ -1976,7 +2002,7 @@ TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { // First drawing tablet is removed mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); assertPointerControllerNotRemoved(pc); // Second drawing tablet is removed @@ -1991,8 +2017,9 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { // Mouse and drawing tablet connected mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE), - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::ADISPLAY_ID_NONE)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_TRUE(pc->isPointerShown()); @@ -2007,7 +2034,7 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { // Remove the mouse device mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); // The mouse controller should not be removed, because the drawing tablet has produced a // mouse event, so we are treating it as a mouse too. diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index 9818176cb0..c273e92de6 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -15,7 +15,6 @@ */ #include -#include #include "../PreferStylusOverTouchBlocker.h" namespace android { @@ -64,8 +63,9 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, } // Define a valid motion event. - NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0, - POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, + NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, + /*displayId=*/ui::ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER, action, + /* actionButton */ 0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0, @@ -439,7 +439,7 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { InputDeviceInfo stylusDevice; stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, /*identifier=*/{}, "stylus device", /*external=*/false, - /*hasMic=*/false, ADISPLAY_ID_NONE); + /*hasMic=*/false, ui::ADISPLAY_ID_NONE); notifyInputDevicesChanged({stylusDevice}); // The touchscreen device was removed, so we no longer remember anything about it. We should // again start blocking touch events from it. diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h index a3e8eafea3..65fb9c6659 100644 --- a/services/inputflinger/tests/TestEventMatchers.h +++ b/services/inputflinger/tests/TestEventMatchers.h @@ -145,7 +145,7 @@ inline WithMotionActionMatcher WithMotionAction(int32_t action) { class WithDisplayIdMatcher { public: using is_gtest_matcher = void; - explicit WithDisplayIdMatcher(int32_t displayId) : mDisplayId(displayId) {} + explicit WithDisplayIdMatcher(ui::LogicalDisplayId displayId) : mDisplayId(displayId) {} bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const { return mDisplayId == args.displayId; @@ -164,10 +164,10 @@ public: void DescribeNegationTo(std::ostream* os) const { *os << "wrong display id"; } private: - const int32_t mDisplayId; + const ui::LogicalDisplayId mDisplayId; }; -inline WithDisplayIdMatcher WithDisplayId(int32_t displayId) { +inline WithDisplayIdMatcher WithDisplayId(ui::LogicalDisplayId displayId) { return WithDisplayIdMatcher(displayId); } diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index 402654c7ac..245497ca4c 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -37,7 +37,7 @@ constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; -constexpr int32_t DISPLAY_ID = 0; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; constexpr std::optional NO_PORT = std::nullopt; // no physical port is specified diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 78f7291243..80430d1d22 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h" @@ -89,7 +88,8 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, // Define a valid motion event. NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, - /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, + /*displayId=*/ui::ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER, action, + /*actionButton=*/0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0, @@ -104,7 +104,7 @@ static InputDeviceInfo generateTestDeviceInfo() { auto info = InputDeviceInfo(); info.initialize(DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias", - /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE); + /*isExternal=*/false, /*hasMic=*/false, ui::ADISPLAY_ID_NONE); info.addSource(AINPUT_SOURCE_TOUCHSCREEN); info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat=*/0, /*fuzz=*/0, X_RESOLUTION); @@ -434,7 +434,7 @@ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListene TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { // Create a basic key event and send to blocker NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3, - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, + AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, AMETA_NONE, /*downTime=*/6); diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h index 885820fafb..812969bb33 100644 --- a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h +++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h @@ -178,7 +178,7 @@ NotifyMotionArgs generateFuzzedMotionArgs(IdGenerator& idGenerator, FuzzedDataPr pointerCoords.push_back(coords); } - const int32_t displayId = fdp.ConsumeIntegralInRange(0, maxDisplays - 1); + const ui::LogicalDisplayId displayId{fdp.ConsumeIntegralInRange(0, maxDisplays - 1)}; const int32_t deviceId = fdp.ConsumeIntegralInRange(0, MAX_RANDOM_DEVICES - 1); // Current time +- 5 seconds diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp index deb811d1ca..0446d76218 100644 --- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -54,7 +54,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral(), eventTime, readTime, /*deviceId=*/fdp.ConsumeIntegral(), - AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, + AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/fdp.ConsumeIntegral(), AKEY_EVENT_ACTION_DOWN, /*flags=*/fdp.ConsumeIntegral(), AKEYCODE_HOME, diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp index 7335fb7500..79a5ff6e5f 100644 --- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp @@ -90,7 +90,7 @@ void scrambleWindow(FuzzedDataProvider& fdp, FakeWindowHandle& window) { sp generateFuzzedWindow(FuzzedDataProvider& fdp, std::unique_ptr& dispatcher, - int32_t displayId) { + ui::LogicalDisplayId displayId) { static size_t windowNumber = 0; std::shared_ptr application = std::make_shared(); std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++); @@ -101,10 +101,11 @@ sp generateFuzzedWindow(FuzzedDataProvider& fdp, return window; } -void randomizeWindows( - std::unordered_map>>& windowsPerDisplay, - FuzzedDataProvider& fdp, std::unique_ptr& dispatcher) { - const int32_t displayId = fdp.ConsumeIntegralInRange(0, MAX_RANDOM_DISPLAYS - 1); +void randomizeWindows(std::unordered_map>>& + windowsPerDisplay, + FuzzedDataProvider& fdp, std::unique_ptr& dispatcher) { + const ui::LogicalDisplayId displayId{ + fdp.ConsumeIntegralInRange(0, MAX_RANDOM_DISPLAYS - 1)}; std::vector>& windows = windowsPerDisplay[displayId]; fdp.PickValueInArray>({ @@ -148,7 +149,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { // Start InputDispatcher thread dispatcher->start(); - std::unordered_map>> windowsPerDisplay; + std::unordered_map>> windowsPerDisplay; // Randomly invoke InputDispatcher api's until randomness is exhausted. while (fdp.remaining_bytes() > 0) { diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index 9223287114..34ea54ca4b 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -119,7 +119,7 @@ public: return reader->getSensors(deviceId); } - bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) { + bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) { return reader->canDispatchToDisplay(deviceId, displayId); } @@ -241,7 +241,8 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { reader->canDispatchToDisplay(fdp->ConsumeIntegral(), - fdp->ConsumeIntegral()); + ui::LogicalDisplayId{ + fdp->ConsumeIntegral()}); }, [&]() -> void { reader->getKeyCodeForKeyLocation(fdp->ConsumeIntegral(), diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index e020ca9d62..25f2f2ec76 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -285,7 +285,7 @@ public: void notifyStylusGestureStarted(int32_t, nsecs_t) {} bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); } std::optional getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) override { + ui::LogicalDisplayId associatedDisplayId) override { return {}; } }; diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 45ab7ddc4d..3bf0eaa0f1 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -137,7 +137,7 @@ void DisplayDevice::setDisplayName(const std::string& displayName) { auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo { gui::DisplayInfo info; - info.displayId = getLayerStack().id; + info.displayId = ui::LogicalDisplayId{static_cast(getLayerStack().id)}; // The physical orientation is set when the orientation of the display panel is // different than the default orientation of the device. Other services like diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h index 6502f36f70..ea51e92e7b 100644 --- a/services/surfaceflinger/FrontEnd/DisplayInfo.h +++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace android::surfaceflinger::frontend { diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 2ff0facdba..e40c79cf62 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -1044,7 +1044,8 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, snapshot.touchCropId = requested.touchCropId; snapshot.inputInfo.id = static_cast(snapshot.uniqueSequence); - snapshot.inputInfo.displayId = static_cast(snapshot.outputFilter.layerStack.id); + snapshot.inputInfo.displayId = + ui::LogicalDisplayId{static_cast(snapshot.outputFilter.layerStack.id)}; snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo() ? requested.windowInfoHandle->getInfo()->touchOcclusionMode : parentSnapshot.inputInfo.touchOcclusionMode; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 0137a7bc50..363b35c4f3 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -90,6 +90,10 @@ constexpr int kDumpTableRowLength = 159; const ui::Transform kIdentityTransform; +ui::LogicalDisplayId toLogicalDisplayId(const ui::LayerStack& layerStack) { + return ui::LogicalDisplayId{static_cast(layerStack.id)}; +} + bool assignTransform(ui::Transform* dst, ui::Transform& from) { if (*dst == from) { return false; @@ -2465,7 +2469,7 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { mDrawingState.inputInfo.ownerUid = gui::Uid{mOwnerUid}; mDrawingState.inputInfo.ownerPid = gui::Pid{mOwnerPid}; mDrawingState.inputInfo.inputConfig |= WindowInfo::InputConfig::NO_INPUT_CHANNEL; - mDrawingState.inputInfo.displayId = getLayerStack().id; + mDrawingState.inputInfo.displayId = toLogicalDisplayId(getLayerStack()); } const ui::Transform& displayTransform = @@ -2473,7 +2477,7 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) { WindowInfo info = mDrawingState.inputInfo; info.id = sequence; - info.displayId = getLayerStack().id; + info.displayId = toLogicalDisplayId(getLayerStack()); fillInputFrameInfo(info, displayTransform); diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 513c943f61..753886ad24 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -465,7 +465,7 @@ LayerProtoHelper::writeDisplayInfoToProto(const frontend::DisplayInfos& displayI displays.Reserve(displayInfos.size()); for (const auto& [layerStack, displayInfo] : displayInfos) { auto displayProto = displays.Add(); - displayProto->set_id(displayInfo.info.displayId); + displayProto->set_id(displayInfo.info.displayId.val()); displayProto->set_layer_stack(layerStack.id); displayProto->mutable_size()->set_w(displayInfo.info.logicalWidth); displayProto->mutable_size()->set_h(displayInfo.info.logicalHeight); diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index 2dc89b55ba..b3e9fab261 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -566,7 +566,7 @@ perfetto::protos::DisplayInfo TransactionProtoParser::toProto( const frontend::DisplayInfo& displayInfo, uint32_t layerStack) { perfetto::protos::DisplayInfo proto; proto.set_layer_stack(layerStack); - proto.set_display_id(displayInfo.info.displayId); + proto.set_display_id(displayInfo.info.displayId.val()); proto.set_logical_width(displayInfo.info.logicalWidth); proto.set_logical_height(displayInfo.info.logicalHeight); asProto(proto.mutable_transform_inverse(), displayInfo.info.transform); @@ -588,7 +588,7 @@ void fromProto2(ui::Transform& outTransform, const perfetto::protos::Transform& frontend::DisplayInfo TransactionProtoParser::fromProto( const perfetto::protos::DisplayInfo& proto) { frontend::DisplayInfo displayInfo; - displayInfo.info.displayId = proto.display_id(); + displayInfo.info.displayId = ui::LogicalDisplayId{proto.display_id()}; displayInfo.info.logicalWidth = proto.logical_width(); displayInfo.info.logicalHeight = proto.logical_height(); fromProto2(displayInfo.info.transform, proto.transform_inverse()); diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp index cbb597af69..af0233063e 100644 --- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp @@ -106,7 +106,7 @@ TEST(TransactionProtoParserTest, parse) { TEST(TransactionProtoParserTest, parseDisplayInfo) { frontend::DisplayInfo d1; - d1.info.displayId = 42; + d1.info.displayId = ui::LogicalDisplayId{42}; d1.info.logicalWidth = 43; d1.info.logicalHeight = 44; d1.info.transform.set(1, 2, 3, 4); -- GitLab From 154fc046ff0faf746a278ef30e04e88b16276b5e Mon Sep 17 00:00:00 2001 From: Xiang Wang Date: Tue, 2 Apr 2024 14:17:53 -0700 Subject: [PATCH 322/465] Add FMQ support in PowerAdvisor Bug: 284324521 Flag: android.os.adpf_use_fmq_channel_fixed Test: atest PowerAdvisorTest Change-Id: Iec3d861d1a7603cbe6b924453344725431573ed7 --- .../DisplayHardware/PowerAdvisor.cpp | 178 ++++++++--- .../DisplayHardware/PowerAdvisor.h | 17 ++ .../surfaceflinger/common/FlagManager.cpp | 1 + .../common/include/common/FlagManager.h | 1 + .../tests/unittests/PowerAdvisorTest.cpp | 278 ++++++++++++++++-- 5 files changed, 410 insertions(+), 65 deletions(-) diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp index 96d5ca6155..6c1a81314d 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp @@ -46,10 +46,21 @@ PowerAdvisor::~PowerAdvisor() = default; namespace impl { using aidl::android::hardware::power::Boost; +using aidl::android::hardware::power::ChannelConfig; using aidl::android::hardware::power::Mode; using aidl::android::hardware::power::SessionHint; using aidl::android::hardware::power::SessionTag; using aidl::android::hardware::power::WorkDuration; +using aidl::android::hardware::power::WorkDurationFixedV1; + +using aidl::android::hardware::common::fmq::MQDescriptor; +using aidl::android::hardware::common::fmq::SynchronizedReadWrite; +using aidl::android::hardware::power::ChannelMessage; +using android::hardware::EventFlag; + +using ChannelMessageContents = ChannelMessage::ChannelMessageContents; +using MsgQueue = android::AidlMessageQueue; +using FlagQueue = android::AidlMessageQueue; PowerAdvisor::~PowerAdvisor() = default; @@ -140,15 +151,7 @@ void PowerAdvisor::notifyCpuLoadUp() { if (!mBootFinished.load()) { return; } - if (usePowerHintSession()) { - std::lock_guard lock(mHintSessionMutex); - if (ensurePowerHintSessionRunning()) { - auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_UP); - if (!ret.isOk()) { - mHintSession = nullptr; - } - } - } + sendHintSessionHint(SessionHint::CPU_LOAD_UP); } void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() { @@ -160,15 +163,7 @@ void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() { if (mSendUpdateImminent.exchange(false)) { ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset"); - if (usePowerHintSession()) { - std::lock_guard lock(mHintSessionMutex); - if (ensurePowerHintSessionRunning()) { - auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET); - if (!ret.isOk()) { - mHintSession = nullptr; - } - } - } + sendHintSessionHint(SessionHint::CPU_LOAD_RESET); if (!mHasDisplayUpdateImminent) { ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it"); @@ -210,6 +205,30 @@ bool PowerAdvisor::shouldCreateSessionWithConfig() { FlagManager::getInstance().adpf_use_fmq_channel(); } +void PowerAdvisor::sendHintSessionHint(SessionHint hint) { + if (!mBootFinished || !usePowerHintSession()) { + ALOGV("Power hint session is not enabled, skip sending session hint"); + return; + } + ATRACE_CALL(); + if (sTraceHintSessionData) ATRACE_INT("Session hint", static_cast(hint)); + { + std::scoped_lock lock(mHintSessionMutex); + if (!ensurePowerHintSessionRunning()) { + ALOGV("Hint session not running and could not be started, skip sending session hint"); + return; + } + ALOGV("Sending session hint: %d", static_cast(hint)); + if (!writeHintSessionMessage(&hint, 1)) { + auto ret = mHintSession->sendHint(hint); + if (!ret.isOk()) { + ALOGW("Failed to send session hint with error: %s", ret.errorMessage()); + mHintSession = nullptr; + } + } + } +} + bool PowerAdvisor::ensurePowerHintSessionRunning() { if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) { if (shouldCreateSessionWithConfig()) { @@ -221,6 +240,9 @@ bool PowerAdvisor::ensurePowerHintSessionRunning() { &mSessionConfig); if (ret.isOk()) { mHintSession = ret.value(); + if (FlagManager::getInstance().adpf_use_fmq_channel_fixed()) { + setUpFmq(); + } } // If it fails the first time we try, or ever returns unsupported, assume unsupported else if (mFirstConfigSupportCheck || ret.isUnsupported()) { @@ -241,9 +263,36 @@ bool PowerAdvisor::ensurePowerHintSessionRunning() { return mHintSession != nullptr; } +void PowerAdvisor::setUpFmq() { + auto&& channelRet = getPowerHal().getSessionChannel(getpid(), static_cast(getuid())); + if (!channelRet.isOk()) { + ALOGE("Failed to get session channel with error: %s", channelRet.errorMessage()); + return; + } + auto& channelConfig = channelRet.value(); + mMsgQueue = std::make_unique(std::move(channelConfig.channelDescriptor), true); + LOG_ALWAYS_FATAL_IF(!mMsgQueue->isValid(), "Failed to set up hint session msg queue"); + LOG_ALWAYS_FATAL_IF(channelConfig.writeFlagBitmask <= 0, + "Invalid flag bit masks found in channel config: writeBitMask(%d)", + channelConfig.writeFlagBitmask); + mFmqWriteMask = static_cast(channelConfig.writeFlagBitmask); + if (!channelConfig.eventFlagDescriptor.has_value()) { + // For FMQ v1 in Android 15 we will force using shared event flag since the default + // no-op FMQ impl in Power HAL v5 will always return a valid channel config with + // non-zero masks but no shared flag. + mMsgQueue = nullptr; + ALOGE("No event flag descriptor found in channel config"); + return; + } + mFlagQueue = std::make_unique(std::move(*channelConfig.eventFlagDescriptor), true); + LOG_ALWAYS_FATAL_IF(!mFlagQueue->isValid(), "Failed to set up hint session flag queue"); + auto status = EventFlag::createEventFlag(mFlagQueue->getEventFlagWord(), &mEventFlag); + LOG_ALWAYS_FATAL_IF(status != OK, "Failed to set up hint session event flag"); +} + void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) { if (!mBootFinished || !usePowerHintSession()) { - ALOGV("Power hint session target duration cannot be set, skipping"); + ALOGV("Power hint session is not enabled, skipping target update"); return; } ATRACE_CALL(); @@ -251,10 +300,15 @@ void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) { mTargetDuration = targetDuration; if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns()); if (targetDuration == mLastTargetDurationSent) return; - std::lock_guard lock(mHintSessionMutex); - if (ensurePowerHintSessionRunning()) { - ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns()); - mLastTargetDurationSent = targetDuration; + std::scoped_lock lock(mHintSessionMutex); + if (!ensurePowerHintSessionRunning()) { + ALOGV("Hint session not running and could not be started, skip updating target"); + return; + } + ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns()); + mLastTargetDurationSent = targetDuration; + auto target = targetDuration.ns(); + if (!writeHintSessionMessage(&target, 1)) { auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns()); if (!ret.isOk()) { ALOGW("Failed to set power hint target work duration with error: %s", @@ -302,23 +356,73 @@ void PowerAdvisor::reportActualWorkDuration() { } { - std::lock_guard lock(mHintSessionMutex); + std::scoped_lock lock(mHintSessionMutex); if (!ensurePowerHintSessionRunning()) { - ALOGV("Hint session not running and could not be started, skipping"); + ALOGV("Hint session not running and could not be started, skip reporting durations"); return; } mHintSessionQueue.push_back(*actualDuration); - - auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue); - if (!ret.isOk()) { - ALOGW("Failed to report actual work durations with error: %s", ret.errorMessage()); - mHintSession = nullptr; - return; + if (!writeHintSessionMessage< + ChannelMessageContents::Tag::workDuration>(mHintSessionQueue.data(), + mHintSessionQueue.size())) { + auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue); + if (!ret.isOk()) { + ALOGW("Failed to report actual work durations with error: %s", ret.errorMessage()); + mHintSession = nullptr; + return; + } } } mHintSessionQueue.clear(); } +template +bool PowerAdvisor::writeHintSessionMessage(In* contents, size_t count) { + if (!mMsgQueue) { + ALOGV("Skip using FMQ with message tag %hhd as it's not supported", T); + return false; + } + auto availableSize = mMsgQueue->availableToWrite(); + if (availableSize < count) { + ALOGW("Skip using FMQ with message tag %hhd as there isn't enough space", T); + return false; + } + MsgQueue::MemTransaction tx; + if (!mMsgQueue->beginWrite(count, &tx)) { + ALOGW("Failed to begin writing message with tag %hhd", T); + return false; + } + for (size_t i = 0; i < count; ++i) { + if constexpr (T == ChannelMessageContents::Tag::workDuration) { + const WorkDuration& duration = contents[i]; + new (tx.getSlot(i)) ChannelMessage{ + .sessionID = static_cast(mSessionConfig.id), + .timeStampNanos = + (i == count - 1) ? ::android::uptimeNanos() : duration.timeStampNanos, + .data = ChannelMessageContents::make({ + .durationNanos = duration.durationNanos, + .workPeriodStartTimestampNanos = duration.workPeriodStartTimestampNanos, + .cpuDurationNanos = duration.cpuDurationNanos, + .gpuDurationNanos = duration.gpuDurationNanos, + }), + }; + } else { + new (tx.getSlot(i)) ChannelMessage{ + .sessionID = static_cast(mSessionConfig.id), + .timeStampNanos = ::android::uptimeNanos(), + .data = ChannelMessageContents::make(std::move(contents[i])), + }; + } + } + if (!mMsgQueue->commitWrite(count)) { + ALOGW("Failed to send message with tag %hhd, fall back to binder call", T); + return false; + } + mEventFlag->wake(mFmqWriteMask); + return true; +} + void PowerAdvisor::enablePowerHintSession(bool enabled) { mHintSessionEnabled = enabled; } @@ -334,12 +438,14 @@ bool PowerAdvisor::startPowerHintSession(std::vector&& threadIds) { } LOG_ALWAYS_FATAL_IF(mHintSessionThreadIds.empty(), "No thread IDs provided to power hint session!"); - std::lock_guard lock(mHintSessionMutex); - if (mHintSession != nullptr) { - ALOGE("Cannot start power hint session: already running"); - return false; + { + std::scoped_lock lock(mHintSessionMutex); + if (mHintSession != nullptr) { + ALOGE("Cannot start power hint session: already running"); + return false; + } + return ensurePowerHintSessionRunning(); } - return ensurePowerHintSessionRunning(); } bool PowerAdvisor::supportsGpuReporting() { diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h index 161ca6372a..bc4a41bf84 100644 --- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h +++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h @@ -29,6 +29,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wconversion" #include +#include #include #pragma clang diagnostic pop @@ -237,6 +238,7 @@ private: bool shouldCreateSessionWithConfig() REQUIRES(mHintSessionMutex); bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex); + void setUpFmq() REQUIRES(mHintSessionMutex); std::unordered_map mDisplayTimingData; // Current frame's delay Duration mFrameDelayDuration{0ns}; @@ -272,6 +274,15 @@ private: bool mHasDisplayUpdateImminent = true; // Queue of actual durations saved to report std::vector mHintSessionQueue; + std::unique_ptr<::android::AidlMessageQueue< + aidl::android::hardware::power::ChannelMessage, + ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>> + mMsgQueue GUARDED_BY(mHintSessionMutex); + std::unique_ptr<::android::AidlMessageQueue< + int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>> + mFlagQueue GUARDED_BY(mHintSessionMutex); + android::hardware::EventFlag* mEventFlag; + uint32_t mFmqWriteMask; // The latest values we have received for target and actual Duration mTargetDuration = kDefaultTargetDuration; // The list of thread ids, stored so we can restart the session from this class if needed @@ -306,6 +317,12 @@ private: // How long we expect hwc to run after the present call until it waits for the fence static constexpr const Duration kFenceWaitStartDelayValidated{150us}; static constexpr const Duration kFenceWaitStartDelaySkippedValidate{250us}; + + void sendHintSessionHint(aidl::android::hardware::power::SessionHint hint); + + template + bool writeHintSessionMessage(In* elements, size_t count) REQUIRES(mHintSessionMutex); }; } // namespace impl diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index f074b7d48a..45b3290b2b 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -249,5 +249,6 @@ FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os) /// Trunk stable readonly flags from outside SurfaceFlinger /// FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "", com::android::server::display::feature::flags) +FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os) } // namespace android diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 0acf754f34..592e7745c9 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -51,6 +51,7 @@ public: bool refresh_rate_overlay_on_external_display() const; bool adpf_gpu_sf() const; bool adpf_use_fmq_channel() const; + bool adpf_use_fmq_channel_fixed() const; /// Trunk stable readonly flags /// bool connected_display() const; diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp index 86ad86eae2..e74f64305c 100644 --- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp +++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp @@ -45,6 +45,7 @@ namespace android::Hwc2::impl { class PowerAdvisorTest : public testing::Test { public: void SetUp() override; + void SetUpFmq(bool usesSharedEventFlag, bool isQueueFull); void startPowerHintSession(bool returnValidSession = true); void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod); void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime); @@ -64,16 +65,26 @@ public: Duration postCompDuration = 0ms; bool frame1RequiresRenderEngine; bool frame2RequiresRenderEngine; + bool usesFmq = false; + bool usesSharedFmqFlag = true; + bool fmqFull = false; }; - WorkDuration testGpuScenario(GpuTestConfig& config); + void testGpuScenario(GpuTestConfig& config, WorkDuration& ret); protected: TestableSurfaceFlinger mFlinger; std::unique_ptr mPowerAdvisor; MockPowerHalController* mMockPowerHalController; std::shared_ptr mMockPowerHintSession; + std::shared_ptr> mBackendFmq; + std::shared_ptr> mBackendFlagQueue; + android::hardware::EventFlag* mEventFlag; + uint32_t mWriteFlagBitmask = 2; + uint32_t mReadFlagBitmask = 1; + int64_t mSessionId = 123; SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, true); + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, false); }; bool PowerAdvisorTest::sessionExists() { @@ -95,12 +106,44 @@ void PowerAdvisorTest::SetUp() { ByMove(HalResult::fromStatus(ndk::ScopedAStatus::ok(), 16000)))); } +void PowerAdvisorTest::SetUpFmq(bool usesSharedEventFlag, bool isQueueFull) { + mBackendFmq = std::make_shared< + AidlMessageQueue>(2, !usesSharedEventFlag); + ChannelConfig config; + config.channelDescriptor = mBackendFmq->dupeDesc(); + if (usesSharedEventFlag) { + mBackendFlagQueue = + std::make_shared>(1, true); + config.eventFlagDescriptor = mBackendFlagQueue->dupeDesc(); + ASSERT_EQ(android::hardware::EventFlag::createEventFlag(mBackendFlagQueue + ->getEventFlagWord(), + &mEventFlag), + android::NO_ERROR); + } else { + ASSERT_EQ(android::hardware::EventFlag::createEventFlag(mBackendFmq->getEventFlagWord(), + &mEventFlag), + android::NO_ERROR); + } + config.writeFlagBitmask = static_cast(mWriteFlagBitmask); + config.readFlagBitmask = static_cast(mReadFlagBitmask); + ON_CALL(*mMockPowerHalController, getSessionChannel) + .WillByDefault(Return( + ByMove(HalResult::fromStatus(Status::ok(), std::move(config))))); + startPowerHintSession(); + if (isQueueFull) { + std::vector msgs; + msgs.resize(2); + mBackendFmq->writeBlocking(msgs.data(), 2, mReadFlagBitmask, mWriteFlagBitmask, + std::chrono::nanoseconds(1ms).count(), mEventFlag); + } +} + void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) { mMockPowerHintSession = std::make_shared>(); if (returnValidSession) { ON_CALL(*mMockPowerHalController, createHintSessionWithConfig) .WillByDefault(DoAll(SetArgPointee<5>(aidl::android::hardware::power::SessionConfig{ - .id = 12}), + .id = mSessionId}), Return(HalResult>:: fromStatus(binder::Status::ok(), mMockPowerHintSession)))); @@ -137,11 +180,17 @@ void PowerAdvisorTest::allowReportActualToAcquireMutex() { mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true); } -WorkDuration PowerAdvisorTest::testGpuScenario(GpuTestConfig& config) { +void PowerAdvisorTest::testGpuScenario(GpuTestConfig& config, WorkDuration& ret) { SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_gpu_sf, config.adpfGpuFlagOn); + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, config.usesFmq); mPowerAdvisor->onBootFinished(); - startPowerHintSession(); + bool expectsFmqSuccess = config.usesSharedFmqFlag && !config.fmqFull; + if (config.usesFmq) { + SetUpFmq(config.usesSharedFmqFlag, config.fmqFull); + } else { + startPowerHintSession(); + } std::vector displayIds{PhysicalDisplayId::fromPort(42u), GpuVirtualDisplayId(0), GpuVirtualDisplayId(1)}; @@ -150,8 +199,41 @@ WorkDuration PowerAdvisorTest::testGpuScenario(GpuTestConfig& config) { // 60hz TimePoint startTime = TimePoint::now(); + int64_t target; + SessionHint hint; + if (!config.usesFmq || !expectsFmqSuccess) { + EXPECT_CALL(*mMockPowerHintSession, updateTargetWorkDuration(_)) + .Times(1) + .WillOnce(DoAll(testing::SaveArg<0>(&target), + testing::Return(testing::ByMove(HalResult::ok())))); + EXPECT_CALL(*mMockPowerHintSession, sendHint(_)) + .Times(1) + .WillOnce(DoAll(testing::SaveArg<0>(&hint), + testing::Return(testing::ByMove(HalResult::ok())))); + } // advisor only starts on frame 2 so do an initial frame fakeBasicFrameTiming(startTime, config.vsyncPeriod); + // send a load hint + mPowerAdvisor->notifyCpuLoadUp(); + if (config.usesFmq && expectsFmqSuccess) { + std::vector msgs; + ASSERT_EQ(mBackendFmq->availableToRead(), 2uL); + msgs.resize(2); + ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 2, mReadFlagBitmask, mWriteFlagBitmask, + std::chrono::nanoseconds(1ms).count(), mEventFlag)); + ASSERT_EQ(msgs[0].sessionID, mSessionId); + ASSERT_GE(msgs[0].timeStampNanos, startTime.ns()); + ASSERT_EQ(msgs[0].data.getTag(), + ChannelMessage::ChannelMessageContents::Tag::targetDuration); + target = msgs[0].data.get(); + ASSERT_EQ(msgs[1].sessionID, mSessionId); + ASSERT_GE(msgs[1].timeStampNanos, startTime.ns()); + ASSERT_EQ(msgs[1].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::hint); + hint = msgs[1].data.get(); + } + ASSERT_EQ(target, config.vsyncPeriod.ns()); + ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP); + setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod); // report GPU @@ -171,6 +253,10 @@ WorkDuration PowerAdvisorTest::testGpuScenario(GpuTestConfig& config) { std::this_thread::sleep_for(config.vsyncPeriod); startTime = TimePoint::now(); fakeBasicFrameTiming(startTime, config.vsyncPeriod); + if (config.usesFmq && expectsFmqSuccess) { + // same target update will not trigger FMQ write + ASSERT_EQ(mBackendFmq->availableToRead(), 0uL); + } setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod); // report GPU @@ -192,14 +278,31 @@ WorkDuration PowerAdvisorTest::testGpuScenario(GpuTestConfig& config) { mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime, startTime); mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime, startTime); - std::vector durationReq; - EXPECT_CALL(*mMockPowerHintSession, reportActualWorkDuration(_)) - .Times(1) - .WillOnce(DoAll(testing::SaveArg<0>(&durationReq), - testing::Return(testing::ByMove(HalResult::ok())))); - mPowerAdvisor->reportActualWorkDuration(); - EXPECT_EQ(durationReq.size(), 1u); - return durationReq[0]; + if (config.usesFmq && expectsFmqSuccess) { + mPowerAdvisor->reportActualWorkDuration(); + ASSERT_EQ(mBackendFmq->availableToRead(), 1uL); + std::vector msgs; + msgs.resize(1); + ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask, + std::chrono::nanoseconds(1ms).count(), mEventFlag)); + ASSERT_EQ(msgs[0].sessionID, mSessionId); + ASSERT_GE(msgs[0].timeStampNanos, startTime.ns()); + ASSERT_EQ(msgs[0].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::workDuration); + auto actual = msgs[0].data.get(); + ret.workPeriodStartTimestampNanos = actual.workPeriodStartTimestampNanos; + ret.cpuDurationNanos = actual.cpuDurationNanos; + ret.gpuDurationNanos = actual.gpuDurationNanos; + ret.durationNanos = actual.durationNanos; + } else { + std::vector durationReq; + EXPECT_CALL(*mMockPowerHintSession, reportActualWorkDuration(_)) + .Times(1) + .WillOnce(DoAll(testing::SaveArg<0>(&durationReq), + testing::Return(testing::ByMove(HalResult::ok())))); + mPowerAdvisor->reportActualWorkDuration(); + ASSERT_EQ(durationReq.size(), 1u); + ret = std::move(durationReq[0]); + } } Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) { @@ -375,13 +478,6 @@ TEST_F(PowerAdvisorTest, hintSessionValidWhenNullFromPowerHAL) { mPowerAdvisor->reportActualWorkDuration(); } -TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) { - EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig(_, _, _, _, _, _)).Times(1); - mPowerAdvisor->onBootFinished(); - startPowerHintSession(); - mPowerAdvisor->startPowerHintSession({1, 2, 3}); -} - TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) { // notifyDisplayUpdateImminentAndCpuReset or notifyCpuLoadUp gets called in background // reportActual gets called during callback and sees true session, passes ensure @@ -464,15 +560,21 @@ TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) { } TEST_F(PowerAdvisorTest, legacyHintSessionCreationStillWorks) { - SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, false); mPowerAdvisor->onBootFinished(); mMockPowerHintSession = std::make_shared>(); + EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig) + .Times(1) + .WillOnce(Return(HalResult>:: + fromStatus(ndk::ScopedAStatus::fromExceptionCode( + EX_UNSUPPORTED_OPERATION), + nullptr))); + EXPECT_CALL(*mMockPowerHalController, createHintSession) .Times(1) .WillOnce(Return(HalResult>:: fromStatus(binder::Status::ok(), mMockPowerHintSession))); mPowerAdvisor->enablePowerHintSession(true); - mPowerAdvisor->startPowerHintSession({1, 2, 3}); + ASSERT_TRUE(mPowerAdvisor->startPowerHintSession({1, 2, 3})); } TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames) { @@ -487,7 +589,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames) { .frame1RequiresRenderEngine = false, .frame2RequiresRenderEngine = true, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, 0L); EXPECT_EQ(res.cpuDurationNanos, 0L); EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin())); @@ -505,7 +608,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames_flagOn) { .frame1RequiresRenderEngine = false, .frame2RequiresRenderEngine = true, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms)); EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms)); EXPECT_EQ(res.durationNanos, toNanos(30ms + getErrorMargin())); @@ -523,7 +627,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames) { .frame1RequiresRenderEngine = true, .frame2RequiresRenderEngine = false, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, 0L); EXPECT_EQ(res.cpuDurationNanos, 0L); EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin())); @@ -540,7 +645,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames_flagOn) { .frame1RequiresRenderEngine = true, .frame2RequiresRenderEngine = false, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, 0L); EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms)); EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin())); @@ -559,7 +665,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFrames) { .frame1RequiresRenderEngine = true, .frame2RequiresRenderEngine = true, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, 0L); EXPECT_EQ(res.cpuDurationNanos, 0L); EXPECT_GE(res.durationNanos, toNanos(50ms + getErrorMargin())); @@ -577,7 +684,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFenceFrames_flagOn) { .frame1RequiresRenderEngine = true, .frame2RequiresRenderEngine = true, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, toNanos(50ms)); EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms)); EXPECT_EQ(res.durationNanos, toNanos(50ms + getErrorMargin())); @@ -594,7 +702,8 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFra .frame1RequiresRenderEngine = true, .frame2RequiresRenderEngine = true, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, 0L); EXPECT_EQ(res.cpuDurationNanos, 0L); EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin())); @@ -612,11 +721,122 @@ TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFra .frame1RequiresRenderEngine = true, .frame2RequiresRenderEngine = true, }; - WorkDuration res = testGpuScenario(config); + WorkDuration res; + testGpuScenario(config, res); + EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms)); + EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms)); + EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin())); +} + +TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration) { + GpuTestConfig config{ + .adpfGpuFlagOn = true, + .frame1GpuFenceDuration = 30ms, + .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING), + .vsyncPeriod = 10ms, + .presentDuration = 22ms, + .postCompDuration = 88ms, + .frame1RequiresRenderEngine = true, + .frame2RequiresRenderEngine = true, + .usesFmq = true, + .usesSharedFmqFlag = true, + }; + WorkDuration res; + testGpuScenario(config, res); + EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms)); + EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms)); + EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin())); +} + +TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration_noSharedFlag) { + GpuTestConfig config{ + .adpfGpuFlagOn = true, + .frame1GpuFenceDuration = 30ms, + .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING), + .vsyncPeriod = 10ms, + .presentDuration = 22ms, + .postCompDuration = 88ms, + .frame1RequiresRenderEngine = true, + .frame2RequiresRenderEngine = true, + .usesFmq = true, + .usesSharedFmqFlag = false, + }; + WorkDuration res; + testGpuScenario(config, res); + EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms)); + EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms)); + EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin())); +} + +TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration_queueFull) { + GpuTestConfig config{.adpfGpuFlagOn = true, + .frame1GpuFenceDuration = 30ms, + .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING), + .vsyncPeriod = 10ms, + .presentDuration = 22ms, + .postCompDuration = 88ms, + .frame1RequiresRenderEngine = true, + .frame2RequiresRenderEngine = true, + .usesFmq = true, + .usesSharedFmqFlag = true, + .fmqFull = true}; + WorkDuration res; + testGpuScenario(config, res); EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms)); EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms)); EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin())); } +TEST_F(PowerAdvisorTest, fmq_sendHint) { + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true); + mPowerAdvisor->onBootFinished(); + SetUpFmq(true, false); + auto startTime = uptimeNanos(); + mPowerAdvisor->notifyCpuLoadUp(); + std::vector msgs; + ASSERT_EQ(mBackendFmq->availableToRead(), 1uL); + msgs.resize(1); + ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask, + std::chrono::nanoseconds(1ms).count(), mEventFlag)); + ASSERT_EQ(msgs[0].sessionID, mSessionId); + ASSERT_GE(msgs[0].timeStampNanos, startTime); + ASSERT_EQ(msgs[0].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::hint); + auto hint = msgs[0].data.get(); + ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP); +} + +TEST_F(PowerAdvisorTest, fmq_sendHint_noSharedFlag) { + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true); + mPowerAdvisor->onBootFinished(); + SetUpFmq(false, false); + SessionHint hint; + EXPECT_CALL(*mMockPowerHintSession, sendHint(_)) + .Times(1) + .WillOnce(DoAll(testing::SaveArg<0>(&hint), + testing::Return(testing::ByMove(HalResult::ok())))); + mPowerAdvisor->notifyCpuLoadUp(); + ASSERT_EQ(mBackendFmq->availableToRead(), 0uL); + ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP); +} + +TEST_F(PowerAdvisorTest, fmq_sendHint_queueFull) { + SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true); + mPowerAdvisor->onBootFinished(); + SetUpFmq(true, true); + ASSERT_EQ(mBackendFmq->availableToRead(), 2uL); + SessionHint hint; + EXPECT_CALL(*mMockPowerHintSession, sendHint(_)) + .Times(1) + .WillOnce(DoAll(testing::SaveArg<0>(&hint), + testing::Return(testing::ByMove(HalResult::ok())))); + std::vector msgs; + msgs.resize(1); + mBackendFmq->writeBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask, + std::chrono::nanoseconds(1ms).count(), mEventFlag); + mPowerAdvisor->notifyCpuLoadUp(); + ASSERT_EQ(mBackendFmq->availableToRead(), 2uL); + ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP); +} + } // namespace } // namespace android::Hwc2::impl -- GitLab From 46952451af194a05155d5880bbee1d63e8e89939 Mon Sep 17 00:00:00 2001 From: Rocky Fang Date: Wed, 8 May 2024 21:42:46 +0000 Subject: [PATCH 323/465] Create flag for event scratch buffer nullability Test: presubmit Bug: 339306599 Change-Id: I9b2efadbfb70b30a04abda2d3095e7f4aa4e8b78 --- services/sensorservice/senserservice_flags.aconfig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig index 8d43f79950..f20b213823 100644 --- a/services/sensorservice/senserservice_flags.aconfig +++ b/services/sensorservice/senserservice_flags.aconfig @@ -13,4 +13,11 @@ flag { namespace: "sensors" description: "This flag controls if the callback onDynamicSensorsDisconnected is implemented or not." bug: "316958439" -} \ No newline at end of file +} + +flag { + name: "sensor_event_connection_send_event_require_nonnull_scratch" + namespace: "sensors" + description: "This flag controls we allow to pass in nullptr as scratch in SensorEventConnection::sendEvents()" + bug: "339306599" +} -- GitLab From 9ac632cb23936f2c1475d5b9d76b0c89a1775fae Mon Sep 17 00:00:00 2001 From: Chris Wailes Date: Thu, 9 May 2024 17:35:21 -0700 Subject: [PATCH 324/465] Remove empty doc comment Test: m libbufferstreams-internal_test Bug: 333887339 Change-Id: I16c93f1f350eb94139045ee1aec7c6af690a3653 --- libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs index 73a15be897..82f528e831 100644 --- a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs +++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! - use crate::{ buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher, BufferSubscriber, Frame, StreamConfig, -- GitLab From fc6eab5d5b3c6372118ba5193bcf5ea7e73a0a67 Mon Sep 17 00:00:00 2001 From: Alan Ding Date: Thu, 2 May 2024 18:28:45 -0700 Subject: [PATCH 325/465] SF: Propagate uniqueID when creating virtual displays This is part of Trunk Stable effort to upstream SF-ARC screen record capabiliy (undiverging http://ag/20003031) which requires mirroring virtual displays to be associated with screen capture sessions using the package name within the unique ID. Creating virtual display with unique ID specified is optional such that it doesn't affect existing consumers who don't need it (i.e. av). Bug: 137375833 Bug: 194863377 Test: libsurfaceflinger_unittest Change-Id: Ia3cd13df07f701593ddc94c196df0b04844cf502 --- libs/gui/SurfaceComposerClient.cpp | 15 +++++----- .../aidl/android/gui/ISurfaceComposer.aidl | 18 ++++++----- libs/gui/include/gui/SurfaceComposerClient.h | 11 ++++--- libs/gui/tests/Surface_test.cpp | 4 +-- services/surfaceflinger/DisplayDevice.h | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 24 ++++++++------- services/surfaceflinger/SurfaceFlinger.h | 24 ++++++++------- .../SurfaceFlinger_CreateDisplayTest.cpp | 30 +++++++++++++++++++ .../tests/unittests/TestableSurfaceFlinger.h | 11 +++++-- 9 files changed, 91 insertions(+), 47 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 4f1356bebb..07664ca701 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -89,6 +89,8 @@ int64_t generateId() { void emptyCallback(nsecs_t, const sp&, const std::vector&) {} } // namespace +const std::string SurfaceComposerClient::kEmpty{}; + ComposerService::ComposerService() : Singleton() { Mutex::Autolock _l(mLock); @@ -1276,14 +1278,13 @@ status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction } // --------------------------------------------------------------------------- -sp SurfaceComposerClient::createDisplay(const String8& displayName, bool secure, - float requestedRefereshRate) { +sp SurfaceComposerClient::createDisplay(const String8& displayName, bool isSecure, + const std::string& uniqueId, + float requestedRefreshRate) { sp display = nullptr; - binder::Status status = - ComposerServiceAIDL::getComposerService()->createDisplay(std::string( - displayName.c_str()), - secure, requestedRefereshRate, - &display); + binder::Status status = ComposerServiceAIDL::getComposerService() + ->createDisplay(std::string(displayName.c_str()), isSecure, + uniqueId, requestedRefreshRate, &display); return status.isOk() ? display : nullptr; } diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index 51e01930d3..cf6752f818 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -73,7 +73,7 @@ interface ISurfaceComposer { void bootFinished(); /** - * Create a display event connection + * Create a display event connection. * * layerHandle * Optional binder handle representing a Layer in SF to associate the new @@ -90,12 +90,14 @@ interface ISurfaceComposer { @nullable ISurfaceComposerClient createConnection(); /** - * Create a virtual display + * Create a virtual display. * * displayName - * The name of the virtual display - * secure - * Whether this virtual display is secure + * The name of the virtual display. + * isSecure + * Whether this virtual display is secure. + * uniqueId + * The unique ID for the display. * requestedRefreshRate * The refresh rate, frames per second, to request on the virtual display. * This is just a request, the actual rate may be adjusted to align well @@ -104,11 +106,11 @@ interface ISurfaceComposer { * * requires ACCESS_SURFACE_FLINGER permission. */ - @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure, - float requestedRefreshRate); + @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean isSecure, + @utf8InCpp String uniqueId, float requestedRefreshRate); /** - * Destroy a virtual display + * Destroy a virtual display. * requires ACCESS_SURFACE_FLINGER permission. */ void destroyDisplay(IBinder display); diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 288882695a..82090fa385 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -18,6 +18,7 @@ #include #include + #include #include #include @@ -374,17 +375,15 @@ public: sp mirrorDisplay(DisplayId displayId); - //! Create a virtual display - static sp createDisplay(const String8& displayName, bool secure, - float requestedRefereshRate = 0); + static const std::string kEmpty; + static sp createDisplay(const String8& displayName, bool isSecure, + const std::string& uniqueId = kEmpty, + float requestedRefreshRate = 0); - //! Destroy a virtual display static void destroyDisplay(const sp& display); - //! Get stable IDs for connected physical displays static std::vector getPhysicalDisplayIds(); - //! Get token for a physical display given its stable ID static sp getPhysicalDisplayToken(PhysicalDisplayId displayId); // Returns StalledTransactionInfo if a transaction from the provided pid has not been applied diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index 577d2394c6..8a7ecabc16 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -673,8 +673,8 @@ public: return binder::Status::ok(); } - binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/, - float /*requestedRefreshRate*/, + binder::Status createDisplay(const std::string& /*displayName*/, bool /*isSecure*/, + const std::string& /*uniqueId*/, float /*requestedRefreshRate*/, sp* /*outDisplay*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index edd57cce91..543a16a399 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -329,6 +329,7 @@ struct DisplayDeviceState { uint32_t width = 0; uint32_t height = 0; std::string displayName; + std::string uniqueId; bool isSecure = false; bool isProtected = false; // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 3ec5f213e0..6b117b8a17 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -577,13 +577,13 @@ void SurfaceFlinger::run() { mScheduler->run(); } -sp SurfaceFlinger::createDisplay(const String8& displayName, bool secure, - float requestedRefreshRate) { +sp SurfaceFlinger::createDisplay(const String8& displayName, bool isSecure, + const std::string& uniqueId, float requestedRefreshRate) { // SurfaceComposerAIDL checks for some permissions, but adding an additional check here. // This is to ensure that only root, system, and graphics can request to create a secure // display. Secure displays can show secure content so we add an additional restriction on it. - const int uid = IPCThreadState::self()->getCallingUid(); - if (secure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) { + const uid_t uid = IPCThreadState::self()->getCallingUid(); + if (isSecure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) { ALOGE("Only privileged processes can create a secure display"); return nullptr; } @@ -607,11 +607,12 @@ sp SurfaceFlinger::createDisplay(const String8& displayName, bool secur Mutex::Autolock _l(mStateLock); // Display ID is assigned when virtual display is allocated by HWC. DisplayDeviceState state; - state.isSecure = secure; + state.isSecure = isSecure; // Set display as protected when marked as secure to ensure no behavior change // TODO (b/314820005): separate as a different arg when creating the display. - state.isProtected = secure; + state.isProtected = isSecure; state.displayName = displayName; + state.uniqueId = uniqueId; state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate); mCurrentState.displays.add(token, state); return token; @@ -9530,7 +9531,8 @@ binder::Status SurfaceComposerAIDL::createConnection(sp* outDisplay) { status_t status = checkAccessPermission(); @@ -9538,7 +9540,7 @@ binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName return binderStatusFromStatusT(status); } String8 displayName8 = String8::format("%s", displayName.c_str()); - *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate); + *outDisplay = mFlinger->createDisplay(displayName8, isSecure, uniqueId, requestedRefreshRate); return binder::Status::ok(); } @@ -9555,10 +9557,10 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* std::vector physicalDisplayIds = mFlinger->getPhysicalDisplayIds(); std::vector displayIds; displayIds.reserve(physicalDisplayIds.size()); - for (auto item : physicalDisplayIds) { - displayIds.push_back(static_cast(item.value)); + for (const auto id : physicalDisplayIds) { + displayIds.push_back(static_cast(id.value)); } - *outDisplayIds = displayIds; + *outDisplayIds = std::move(displayIds); return binder::Status::ok(); } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index c106abda37..4c53faec7c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -536,15 +536,16 @@ private: static const size_t MAX_LAYERS = 4096; - // Implements IBinder. + static bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true) + EXCLUDES(mStateLock); + + // IBinder overrides: status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; status_t dump(int fd, const Vector& args) override { return priorityDump(fd, args); } - bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true) - EXCLUDES(mStateLock); - // Implements ISurfaceComposer - sp createDisplay(const String8& displayName, bool secure, - float requestedRefreshRate = 0.0f); + // ISurfaceComposer implementation: + sp createDisplay(const String8& displayName, bool isSecure, + const std::string& uniqueId, float requestedRefreshRate = 0.0f); void destroyDisplay(const sp& displayToken); std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { Mutex::Autolock lock(mStateLock); @@ -665,7 +666,7 @@ private: void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel); - // Implements IBinder::DeathRecipient. + // IBinder::DeathRecipient overrides: void binderDied(const wp& who) override; // HWC2::ComposerCallback overrides: @@ -1543,7 +1544,7 @@ private: class SurfaceComposerAIDL : public gui::BnSurfaceComposer { public: - SurfaceComposerAIDL(sp sf) : mFlinger(std::move(sf)) {} + explicit SurfaceComposerAIDL(sp sf) : mFlinger(std::move(sf)) {} binder::Status bootFinished() override; binder::Status createDisplayEventConnection( @@ -1551,8 +1552,9 @@ public: const sp& layerHandle, sp* outConnection) override; binder::Status createConnection(sp* outClient) override; - binder::Status createDisplay(const std::string& displayName, bool secure, - float requestedRefreshRate, sp* outDisplay) override; + binder::Status createDisplay(const std::string& displayName, bool isSecure, + const std::string& uniqueId, float requestedRefreshRate, + sp* outDisplay) override; binder::Status destroyDisplay(const sp& display) override; binder::Status getPhysicalDisplayIds(std::vector* outDisplayIds) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; @@ -1675,7 +1677,7 @@ private: gui::DynamicDisplayInfo*& outInfo); private: - sp mFlinger; + const sp mFlinger; }; } // namespace android diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp index 28162f4d6b..bf5ae21f60 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp @@ -132,6 +132,36 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); } +TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) { + const String8 name("virtual.test"); + const std::string uniqueId = "virtual:package:id"; + + // -------------------------------------------------------------------- + // Call Expectations + + // -------------------------------------------------------------------- + // Invocation + + sp displayToken = mFlinger.createDisplay(name, false, uniqueId); + + // -------------------------------------------------------------------- + // Postconditions + + // The display should have been added to the current state + ASSERT_TRUE(hasCurrentDisplayState(displayToken)); + const auto& display = getCurrentDisplayState(displayToken); + EXPECT_TRUE(display.isVirtual()); + EXPECT_FALSE(display.isSecure); + EXPECT_EQ(display.uniqueId, "virtual:package:id"); + EXPECT_EQ(name.c_str(), display.displayName); + + // -------------------------------------------------------------------- + // Cleanup conditions + + // Creating the display commits a display transaction. + EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1); +} + // Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRate0) { const String8 displayName("virtual.test"); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 82023b092a..3b03f92090 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -411,8 +411,15 @@ public: commit(kComposite); } - auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) { - return mFlinger->createDisplay(displayName, secure, requestedRefreshRate); + auto createDisplay(const String8& displayName, bool isSecure, + float requestedRefreshRate = 0.0f) { + const std::string testId = "virtual:libsurfaceflinger_unittest:TestableSurfaceFlinger"; + return mFlinger->createDisplay(displayName, isSecure, testId, requestedRefreshRate); + } + + auto createDisplay(const String8& displayName, bool isSecure, const std::string& uniqueId, + float requestedRefreshRate = 0.0f) { + return mFlinger->createDisplay(displayName, isSecure, uniqueId, requestedRefreshRate); } auto destroyDisplay(const sp& displayToken) { -- GitLab From b0b5b6c5727dff214964963b57a4338d799529c1 Mon Sep 17 00:00:00 2001 From: Cody Heiner Date: Thu, 9 May 2024 18:31:12 -0700 Subject: [PATCH 326/465] Use libstatssocket_lazy to report stylus metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report stylus prediction metrics using libstatssocket_lazy as an interim solution while I working on untangling the build dependency issues (b/338106546) that prevent us from using libstatslog within libinput. Test: `adb reboot`, then `adb logcat | egrep -i "statssocket|MotionPredictor"` → Shows no bootanimation linker error, and no other errors Test: `statsd_testdrive -d 3000 718`, then draw with stylus → Shows reasonable metrics logged Bug: 338106546 Bug: 311066949 Change-Id: I05ae5ffdd774d0b25cb7b2435015245d5f712c01 --- libs/input/Android.bp | 44 ++++++++++++++++++++ libs/input/MotionPredictorMetricsManager.cpp | 20 +++++++-- libs/input/tests/Android.bp | 6 +++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/libs/input/Android.bp b/libs/input/Android.bp index cc0649cc91..8f44b3a0e1 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -289,6 +289,10 @@ cc_library { "motion_predictor_model_prebuilt", "motion_predictor_model_config", ], + static_libs: [ + "libstatslog_libinput", + "libstatssocket_lazy", + ], }, host: { include_dirs: [ @@ -299,6 +303,46 @@ cc_library { }, } +cc_library_static { + name: "libstatslog_libinput", + generated_sources: ["statslog_libinput.cpp"], + generated_headers: ["statslog_libinput.h"], + cflags: [ + "-Wall", + "-Werror", + ], + export_generated_headers: ["statslog_libinput.h"], + shared_libs: [ + "libcutils", + "liblog", + "libutils", + ], + static_libs: [ + "libstatssocket_lazy", + ], +} + +genrule { + name: "statslog_libinput.h", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h " + + "--module libinput --namespace android,libinput", + out: [ + "statslog_libinput.h", + ], +} + +genrule { + name: "statslog_libinput.cpp", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp " + + "--module libinput --namespace android,libinput " + + "--importHeader statslog_libinput.h", + out: [ + "statslog_libinput.cpp", + ], +} + cc_defaults { name: "libinput_fuzz_defaults", cpp_std: "c++20", diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp index cda39ce601..ccf018e56a 100644 --- a/libs/input/MotionPredictorMetricsManager.cpp +++ b/libs/input/MotionPredictorMetricsManager.cpp @@ -21,6 +21,9 @@ #include #include +#ifdef __ANDROID__ +#include +#endif // __ANDROID__ #include "Eigen/Core" #include "Eigen/Geometry" @@ -44,9 +47,20 @@ inline constexpr float PATH_LENGTH_EPSILON = 0.001; void MotionPredictorMetricsManager::defaultReportAtomFunction( const MotionPredictorMetricsManager::AtomFields& atomFields) { - // TODO(b/338106546): Fix bootanimation build dependency issue, then re-add - // the stats_write function call here. - (void)atomFields; +#ifdef __ANDROID__ + android::libinput::stats_write(android::libinput::STYLUS_PREDICTION_METRICS_REPORTED, + /*stylus_vendor_id=*/0, + /*stylus_product_id=*/0, + atomFields.deltaTimeBucketMilliseconds, + atomFields.alongTrajectoryErrorMeanMillipixels, + atomFields.alongTrajectoryErrorStdMillipixels, + atomFields.offTrajectoryRmseMillipixels, + atomFields.pressureRmseMilliunits, + atomFields.highVelocityAlongTrajectoryRmse, + atomFields.highVelocityOffTrajectoryRmse, + atomFields.scaleInvariantAlongTrajectoryRmse, + atomFields.scaleInvariantOffTrajectoryRmse); +#endif // __ANDROID__ } MotionPredictorMetricsManager::MotionPredictorMetricsManager( diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 6e724acc3c..e9d799ed3f 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -79,6 +79,12 @@ cc_test { }, test_suites: ["device-tests"], target: { + android: { + static_libs: [ + "libstatslog_libinput", + "libstatssocket_lazy", + ], + }, host: { sanitize: { address: true, -- GitLab From 870f4e7e579658f03b5c09a9928d35830bb7986f Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 10 May 2024 20:59:35 +0000 Subject: [PATCH 327/465] InputDispatcher: check consistency of filtered injected events -- try 3 This reverts commit d604df195f5d5f59d61b5ef68e1434d99037e2c2. Reason for revert: fixed flags in b/309829647 Change-Id: I191eb04c95d1316cb505ba0861669ad004d02d67 --- .../dispatcher/InputDispatcher.cpp | 26 +++++++++++++++++++ .../inputflinger/dispatcher/InputDispatcher.h | 4 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index c4bfa624f5..8e0da1470e 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4732,6 +4732,7 @@ void InputDispatcher::notifySwitch(const NotifySwitchArgs& args) { } void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) { + // TODO(b/308677868) Remove device reset from the InputListener interface if (debugInboundEventDetails()) { ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime, args.deviceId); @@ -4883,6 +4884,30 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev } mLock.lock(); + + if (policyFlags & POLICY_FLAG_FILTERED) { + // The events from InputFilter impersonate real hardware devices. Check these + // events for consistency and print an error. An inconsistent event sent from + // InputFilter could cause a crash in the later stages of dispatching pipeline. + auto [it, _] = + mInputFilterVerifiersByDisplay + .try_emplace(displayId, + StringPrintf("Injection on %" PRId32, displayId)); + InputVerifier& verifier = it->second; + + Result result = + verifier.processMovement(resolvedDeviceId, motionEvent.getSource(), + motionEvent.getAction(), + motionEvent.getPointerCount(), + motionEvent.getPointerProperties(), + motionEvent.getSamplePointerCoords(), flags); + if (!result.ok()) { + logDispatchStateLocked(); + LOG(ERROR) << "Inconsistent event: " << motionEvent + << ", reason: " << result.error(); + } + } + const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes(); const size_t pointerCount = motionEvent.getPointerCount(); const std::vector @@ -6962,6 +6987,7 @@ void InputDispatcher::displayRemoved(ui::LogicalDisplayId displayId) { // Remove the associated touch mode state. mTouchModePerDisplay.erase(displayId); mVerifiersByDisplay.erase(displayId); + mInputFilterVerifiersByDisplay.erase(displayId); } // release lock // Wake up poll loop since it may need to make new input dispatching choices. diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index f671ea71f4..6240e7fe72 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -301,7 +301,9 @@ private: void transformMotionEntryForInjectionLocked(MotionEntry&, const ui::Transform& injectedTransform) const REQUIRES(mLock); - + // Per-display correction of injected events + std::map mInputFilterVerifiersByDisplay + GUARDED_BY(mLock); std::condition_variable mInjectionSyncFinished; void incrementPendingForegroundDispatches(const EventEntry& entry); void decrementPendingForegroundDispatches(const EventEntry& entry); -- GitLab From b58c0c31025bc458204f35ae11f8b343535d27c6 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 10 May 2024 17:24:06 -0700 Subject: [PATCH 328/465] Remove __linux__ ifdefs where not needed The downstream branches now know about Parcel and binder, so in some places, these #ifdef statements are not needed. This CL should help reduce the divergence with downstream efforts. See the merge conflict resolution CL as an example: https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/native/+/27201796/-2..3 Bug: 309829647 Test: presubmit Change-Id: Iec17cface5069ca79a565c205546db8ada8a07ee --- include/input/Input.h | 20 ++------------------ include/input/KeyCharacterMap.h | 4 ---- libs/input/Input.cpp | 6 ------ libs/input/KeyCharacterMap.cpp | 6 +----- 4 files changed, 3 insertions(+), 33 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 092a6982ed..e939145bbf 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -41,13 +41,11 @@ * Additional private constants not defined in ndk/ui/input.h. */ enum { -#ifdef __linux__ + /* This event was generated or modified by accessibility service. */ AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, -#else - AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800, -#endif + /* Signifies that the key is being predispatched */ AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000, @@ -90,15 +88,11 @@ enum { AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE, -#if defined(__linux__) /** * This event was generated or modified by accessibility service. */ AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, -#else - AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800, -#endif AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS = android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS, @@ -204,9 +198,7 @@ struct AInputDevice { namespace android { -#ifdef __linux__ class Parcel; -#endif /* * Apply the given transform to the point without applying any translation/offset. @@ -312,12 +304,8 @@ enum { POLICY_FLAG_RAW_MASK = 0x0000ffff, -#ifdef __linux__ POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = android::os::IInputConstants::POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY, -#else - POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000, -#endif /* These flags are set by the input dispatcher. */ @@ -486,10 +474,8 @@ struct PointerCoords { vec2 getXYValue() const { return vec2(getX(), getY()); } -#ifdef __linux__ status_t readFromParcel(Parcel* parcel); status_t writeToParcel(Parcel* parcel) const; -#endif bool operator==(const PointerCoords& other) const; inline bool operator!=(const PointerCoords& other) const { @@ -912,10 +898,8 @@ public: // Matrix is in row-major form and compatible with SkMatrix. void applyTransform(const std::array& matrix); -#ifdef __linux__ status_t readFromParcel(Parcel* parcel); status_t writeToParcel(Parcel* parcel) const; -#endif static bool isTouchEvent(uint32_t source, int32_t action); inline bool isTouchEvent() const { diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index dfcf766402..92d5ec4d4e 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -19,9 +19,7 @@ #include #include -#ifdef __linux__ #include -#endif #include #include @@ -144,13 +142,11 @@ public: std::pair applyKeyBehavior(int32_t keyCode, int32_t metaState) const; -#ifdef __linux__ /* Reads a key map from a parcel. */ static std::unique_ptr readFromParcel(Parcel* parcel); /* Writes a key map to a parcel. */ void writeToParcel(Parcel* parcel) const; -#endif bool operator==(const KeyCharacterMap& other) const = default; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index d27156383a..1178d02510 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -32,9 +32,7 @@ #include #include -#ifdef __linux__ #include -#endif #if defined(__ANDROID__) #include #endif @@ -483,7 +481,6 @@ void PointerCoords::scale(float globalScaleFactor, float windowXScale, float win scaleAxisValue(*this, AMOTION_EVENT_AXIS_RELATIVE_Y, windowYScale); } -#ifdef __linux__ status_t PointerCoords::readFromParcel(Parcel* parcel) { bits = parcel->readInt64(); @@ -511,7 +508,6 @@ status_t PointerCoords::writeToParcel(Parcel* parcel) const { parcel->writeBool(isResampled); return OK; } -#endif void PointerCoords::tooManyAxes(int axis) { ALOGW("Could not set value for axis %d because the PointerCoords structure is full and " @@ -801,7 +797,6 @@ void MotionEvent::applyTransform(const std::array& matrix) { } } -#ifdef __linux__ static status_t readFromParcel(ui::Transform& transform, const Parcel& parcel) { float dsdx, dtdx, tx, dtdy, dsdy, ty; status_t status = parcel.readFloat(&dsdx); @@ -948,7 +943,6 @@ status_t MotionEvent::writeToParcel(Parcel* parcel) const { } return OK; } -#endif bool MotionEvent::isTouchEvent(uint32_t source, int32_t action) { if (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER)) { diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index 41909bfb2d..f75bf410f2 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -19,11 +19,9 @@ #include #include -#ifdef __linux__ -#include -#endif #include #include +#include #include #include #include @@ -611,7 +609,6 @@ void KeyCharacterMap::addLockedMetaKey(Vector& outEvents, } } -#ifdef __linux__ std::unique_ptr KeyCharacterMap::readFromParcel(Parcel* parcel) { if (parcel == nullptr) { ALOGE("%s: Null parcel", __func__); @@ -744,7 +741,6 @@ void KeyCharacterMap::writeToParcel(Parcel* parcel) const { parcel->writeInt32(toAndroidKeyCode); } } -#endif // __linux__ // --- KeyCharacterMap::Parser --- -- GitLab From 22d59cc507f4883496406916e4de22a70866cfee Mon Sep 17 00:00:00 2001 From: Ying Wei Date: Sat, 11 May 2024 02:41:08 +0000 Subject: [PATCH 329/465] Rename RefreshRateOverlay vsyncRate to RefreshRate With MRR devices, vsyncRate and refreshRate are the same thing, but this is not true for dVRR devices. Test: manually test refresh rate indicator Change-Id: I0946ad4164257b1919035a0db1080ec06ba1ed53 --- services/surfaceflinger/DisplayDevice.cpp | 4 +-- services/surfaceflinger/DisplayDevice.h | 2 +- .../surfaceflinger/RefreshRateOverlay.cpp | 27 ++++++++++--------- services/surfaceflinger/RefreshRateOverlay.h | 6 ++--- services/surfaceflinger/SurfaceFlinger.cpp | 15 ++++++----- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 3bf0eaa0f1..38cf05327f 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -485,11 +485,11 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool sh } } -void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) { +void DisplayDevice::updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc) { ATRACE_CALL(); if (mRefreshRateOverlay) { if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) { - mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps); + mRefreshRateOverlay->changeRefreshRate(refreshRate, renderFps); } else { mRefreshRateOverlay->changeRenderRate(renderFps); } diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index fc5089b6d3..db1b23de28 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -226,7 +226,7 @@ public: // Enables an overlay to be displayed with the current refresh rate void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate, bool showInMiddle) REQUIRES(kMainThreadContext); - void updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc = false); + void updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc = false); bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } bool onKernelTimerChanged(std::optional, bool timerExpired); diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index b960e33682..b40f3323bc 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -28,7 +28,7 @@ namespace android { -auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color, +auto RefreshRateOverlay::draw(int refreshRate, int renderFps, SkColor color, ui::Transform::RotationFlags rotation, ftl::Flags features) -> Buffers { const size_t loopCount = features.test(Features::Spinner) ? 6 : 1; @@ -71,7 +71,7 @@ auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color, canvas->setMatrix(canvasTransform); int left = 0; - drawNumber(vsyncRate, left, color, *canvas); + drawNumber(refreshRate, left, color, *canvas); left += 3 * (kDigitWidth + kDigitSpace); if (features.test(Features::Spinner)) { switch (i) { @@ -171,7 +171,7 @@ bool RefreshRateOverlay::initCheck() const { return mSurfaceControl != nullptr; } -auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, Fps renderFps) -> const Buffers& { +auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> const Buffers& { static const Buffers kNoBuffers; if (!mSurfaceControl) return kNoBuffers; @@ -198,16 +198,16 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, Fps renderFps) -> con createTransaction().setTransform(mSurfaceControl->get(), transform).apply(); BufferCache::const_iterator it = - mBufferCache.find({vsyncRate.getIntValue(), renderFps.getIntValue(), transformHint}); + mBufferCache.find({refreshRate.getIntValue(), renderFps.getIntValue(), transformHint}); if (it == mBufferCache.end()) { // HWC minFps is not known by the framework in order // to consider lower rates we set minFps to 0. const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue(); const int maxFps = mFpsRange.max.getIntValue(); - // Clamp to the range. The current vsyncRate may be outside of this range if the display + // Clamp to the range. The current refreshRate may be outside of this range if the display // has changed its set of supported refresh rates. - const int displayIntFps = std::clamp(vsyncRate.getIntValue(), minFps, maxFps); + const int displayIntFps = std::clamp(refreshRate.getIntValue(), minFps, maxFps); const int renderIntFps = renderFps.getIntValue(); // Ensure non-zero range to avoid division by zero. @@ -260,25 +260,26 @@ void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) { createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply(); } -void RefreshRateOverlay::changeRefreshRate(Fps vsyncRate, Fps renderFps) { - mVsyncRate = vsyncRate; +void RefreshRateOverlay::changeRefreshRate(Fps refreshRate, Fps renderFps) { + mRefreshRate = refreshRate; mRenderFps = renderFps; - const auto buffer = getOrCreateBuffers(vsyncRate, renderFps)[mFrame]; + const auto buffer = getOrCreateBuffers(refreshRate, renderFps)[mFrame]; createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } void RefreshRateOverlay::changeRenderRate(Fps renderFps) { - if (mFeatures.test(Features::RenderRate) && mVsyncRate && FlagManager::getInstance().misc1()) { + if (mFeatures.test(Features::RenderRate) && mRefreshRate && + FlagManager::getInstance().misc1()) { mRenderFps = renderFps; - const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame]; + const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps)[mFrame]; createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } } void RefreshRateOverlay::animate() { - if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return; + if (!mFeatures.test(Features::Spinner) || !mRefreshRate) return; - const auto& buffers = getOrCreateBuffers(*mVsyncRate, *mRenderFps); + const auto& buffers = getOrCreateBuffers(*mRefreshRate, *mRenderFps); mFrame = (mFrame + 1) % buffers.size(); const auto buffer = buffers[mFrame]; createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index 0fec470edc..93ec36e7f8 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -74,12 +74,12 @@ private: SurfaceComposerClient::Transaction createTransaction() const; struct Key { - int vsyncRate; + int refreshRate; int renderFps; ui::Transform::RotationFlags flags; bool operator==(Key other) const { - return vsyncRate == other.vsyncRate && renderFps == other.renderFps && + return refreshRate == other.refreshRate && renderFps == other.renderFps && flags == other.flags; } }; @@ -87,7 +87,7 @@ private: using BufferCache = ftl::SmallMap; BufferCache mBufferCache; - std::optional mVsyncRate; + std::optional mRefreshRate; std::optional mRenderFps; size_t mFrame = 0; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 0d2e5142bc..029c052486 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2230,12 +2230,12 @@ void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData kMainThreadContext) { if (const auto displayIdOpt = getHwComposer().toPhysicalDisplayId(data.display)) { if (const auto display = getDisplayDeviceLocked(*displayIdOpt)) { - const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported() - ? data.refreshPeriodNanos - : data.vsyncPeriodNanos); - ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue()); - display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps, - /* setByHwc */ true); + const Fps refreshRate = Fps::fromPeriodNsecs( + getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos + : data.vsyncPeriodNanos); + ATRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue()); + display->updateRefreshRateOverlayRate(refreshRate, display->getActiveMode().fps, + /* showRefreshRate */ true); } } })); @@ -8833,7 +8833,8 @@ void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { const auto status = getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable); if (status != NO_ERROR) { - ALOGE("Error updating the refresh rate changed callback debug enabled"); + ALOGE("Error %s refresh rate changed callback debug", + enable ? "enabling" : "disabling"); enableOverlay(/*setByHwc*/ false); } } -- GitLab From c06a3b6c95063c104cf16ccbc2e7c51b0c7f9728 Mon Sep 17 00:00:00 2001 From: Alan Ding Date: Fri, 10 May 2024 20:02:43 -0700 Subject: [PATCH 330/465] SF: Cleanup surfaceplayer Missed a file during previous surfaceplayer cleanup. Bug: 339525838 Bug: 137375833 Bug: 194863377 Test: libsurfaceflinger_unittest Flag: EXEMPT refactor Change-Id: I635eeefc242bfa35d5b88fbab5e029810713db70 --- cmds/surfacereplayer/replayer/Replayer.cpp | 707 --------------------- 1 file changed, 707 deletions(-) delete mode 100644 cmds/surfacereplayer/replayer/Replayer.cpp diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp deleted file mode 100644 index 44235ccdef..0000000000 --- a/cmds/surfacereplayer/replayer/Replayer.cpp +++ /dev/null @@ -1,707 +0,0 @@ -/* Copyright 2016 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. - */ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "SurfaceReplayer" - -#include "Replayer.h" - -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace android; - -std::atomic_bool Replayer::sReplayingManually(false); - -Replayer::Replayer(const std::string& filename, bool replayManually, int numThreads, bool wait, - nsecs_t stopHere) - : mTrace(), - mLoaded(false), - mIncrementIndex(0), - mCurrentTime(0), - mNumThreads(numThreads), - mWaitForTimeStamps(wait), - mStopTimeStamp(stopHere) { - srand(RAND_COLOR_SEED); - - std::string input; - if (!android::base::ReadFileToString(filename, &input, true)) { - std::cerr << "Trace did not load. Does " << filename << " exist?" << std::endl; - abort(); - } - - mLoaded = mTrace.ParseFromString(input); - if (!mLoaded) { - std::cerr << "Trace did not load." << std::endl; - abort(); - } - - mCurrentTime = mTrace.increment(0).time_stamp(); - - sReplayingManually.store(replayManually); - - if (stopHere < 0) { - mHasStopped = true; - } -} - -Replayer::Replayer(const Trace& t, bool replayManually, int numThreads, bool wait, nsecs_t stopHere) - : mTrace(t), - mLoaded(true), - mIncrementIndex(0), - mCurrentTime(0), - mNumThreads(numThreads), - mWaitForTimeStamps(wait), - mStopTimeStamp(stopHere) { - srand(RAND_COLOR_SEED); - mCurrentTime = mTrace.increment(0).time_stamp(); - - sReplayingManually.store(replayManually); - - if (stopHere < 0) { - mHasStopped = true; - } -} - -status_t Replayer::replay() { - signal(SIGINT, Replayer::stopAutoReplayHandler); //for manual control - - ALOGV("There are %d increments.", mTrace.increment_size()); - - status_t status = loadSurfaceComposerClient(); - - if (status != NO_ERROR) { - ALOGE("Couldn't create SurfaceComposerClient (%d)", status); - return status; - } - - SurfaceComposerClient::enableVSyncInjections(true); - - initReplay(); - - ALOGV("Starting actual Replay!"); - while (!mPendingIncrements.empty()) { - mCurrentIncrement = mTrace.increment(mIncrementIndex); - - if (mHasStopped == false && mCurrentIncrement.time_stamp() >= mStopTimeStamp) { - mHasStopped = true; - sReplayingManually.store(true); - } - - waitForConsoleCommmand(); - - if (mWaitForTimeStamps) { - waitUntilTimestamp(mCurrentIncrement.time_stamp()); - } - - auto event = mPendingIncrements.front(); - mPendingIncrements.pop(); - - event->complete(); - - if (event->getIncrementType() == Increment::kVsyncEvent) { - mWaitingForNextVSync = false; - } - - if (mIncrementIndex + mNumThreads < mTrace.increment_size()) { - status = dispatchEvent(mIncrementIndex + mNumThreads); - - if (status != NO_ERROR) { - SurfaceComposerClient::enableVSyncInjections(false); - return status; - } - } - - mIncrementIndex++; - mCurrentTime = mCurrentIncrement.time_stamp(); - } - - SurfaceComposerClient::enableVSyncInjections(false); - - return status; -} - -status_t Replayer::initReplay() { - for (int i = 0; i < mNumThreads && i < mTrace.increment_size(); i++) { - status_t status = dispatchEvent(i); - - if (status != NO_ERROR) { - ALOGE("Unable to dispatch event (%d)", status); - return status; - } - } - - return NO_ERROR; -} - -void Replayer::stopAutoReplayHandler(int /*signal*/) { - if (sReplayingManually) { - SurfaceComposerClient::enableVSyncInjections(false); - exit(0); - } - - sReplayingManually.store(true); -} - -std::vector split(const std::string& s, const char delim) { - std::vector elems; - std::stringstream ss(s); - std::string item; - while (getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -bool isNumber(const std::string& s) { - return !s.empty() && - std::find_if(s.begin(), s.end(), [](char c) { return !std::isdigit(c); }) == s.end(); -} - -void Replayer::waitForConsoleCommmand() { - if (!sReplayingManually || mWaitingForNextVSync) { - return; - } - - while (true) { - std::string input = ""; - std::cout << "> "; - getline(std::cin, input); - - if (input.empty()) { - input = mLastInput; - } else { - mLastInput = input; - } - - if (mLastInput.empty()) { - continue; - } - - std::vector inputs = split(input, ' '); - - if (inputs[0] == "n") { // next vsync - mWaitingForNextVSync = true; - break; - - } else if (inputs[0] == "ni") { // next increment - break; - - } else if (inputs[0] == "c") { // continue - if (inputs.size() > 1 && isNumber(inputs[1])) { - long milliseconds = stoi(inputs[1]); - std::thread([&] { - std::cout << "Started!" << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); - sReplayingManually.store(true); - std::cout << "Should have stopped!" << std::endl; - }).detach(); - } - sReplayingManually.store(false); - mWaitingForNextVSync = false; - break; - - } else if (inputs[0] == "s") { // stop at this timestamp - if (inputs.size() < 1) { - std::cout << "No time stamp given" << std::endl; - continue; - } - sReplayingManually.store(false); - mStopTimeStamp = stol(inputs[1]); - mHasStopped = false; - break; - } else if (inputs[0] == "l") { // list - std::cout << "Time stamp: " << mCurrentIncrement.time_stamp() << "\n"; - continue; - } else if (inputs[0] == "q") { // quit - SurfaceComposerClient::enableVSyncInjections(false); - exit(0); - - } else if (inputs[0] == "h") { // help - // add help menu - std::cout << "Manual Replay options:\n"; - std::cout << " n - Go to next VSync\n"; - std::cout << " ni - Go to next increment\n"; - std::cout << " c - Continue\n"; - std::cout << " c [milliseconds] - Continue until specified number of milliseconds\n"; - std::cout << " s [timestamp] - Continue and stop at specified timestamp\n"; - std::cout << " l - List out timestamp of current increment\n"; - std::cout << " h - Display help menu\n"; - std::cout << std::endl; - continue; - } - - std::cout << "Invalid Command" << std::endl; - } -} - -status_t Replayer::dispatchEvent(int index) { - auto increment = mTrace.increment(index); - std::shared_ptr event = std::make_shared(increment.increment_case()); - mPendingIncrements.push(event); - - status_t status = NO_ERROR; - switch (increment.increment_case()) { - case increment.kTransaction: { - std::thread(&Replayer::doTransaction, this, increment.transaction(), event).detach(); - } break; - case increment.kSurfaceCreation: { - std::thread(&Replayer::createSurfaceControl, this, increment.surface_creation(), event) - .detach(); - } break; - case increment.kBufferUpdate: { - std::lock_guard lock1(mLayerLock); - std::lock_guard lock2(mBufferQueueSchedulerLock); - - Dimensions dimensions(increment.buffer_update().w(), increment.buffer_update().h()); - BufferEvent bufferEvent(event, dimensions); - - auto layerId = increment.buffer_update().id(); - if (mBufferQueueSchedulers.count(layerId) == 0) { - mBufferQueueSchedulers[layerId] = std::make_shared( - mLayers[layerId], mColors[layerId], layerId); - mBufferQueueSchedulers[layerId]->addEvent(bufferEvent); - - std::thread(&BufferQueueScheduler::startScheduling, - mBufferQueueSchedulers[increment.buffer_update().id()].get()) - .detach(); - } else { - auto bqs = mBufferQueueSchedulers[increment.buffer_update().id()]; - bqs->addEvent(bufferEvent); - } - } break; - case increment.kVsyncEvent: { - std::thread(&Replayer::injectVSyncEvent, this, increment.vsync_event(), event).detach(); - } break; - case increment.kDisplayCreation: { - std::thread(&Replayer::createDisplay, this, increment.display_creation(), event) - .detach(); - } break; - case increment.kDisplayDeletion: { - std::thread(&Replayer::deleteDisplay, this, increment.display_deletion(), event) - .detach(); - } break; - case increment.kPowerModeUpdate: { - std::thread(&Replayer::updatePowerMode, this, increment.power_mode_update(), event) - .detach(); - } break; - default: - ALOGE("Unknown Increment Type: %d", increment.increment_case()); - status = BAD_VALUE; - break; - } - - return status; -} - -status_t Replayer::doTransaction(const Transaction& t, const std::shared_ptr& event) { - ALOGV("Started Transaction"); - - SurfaceComposerClient::Transaction liveTransaction; - - status_t status = NO_ERROR; - - status = doSurfaceTransaction(liveTransaction, t.surface_change()); - doDisplayTransaction(liveTransaction, t.display_change()); - - if (t.animation()) { - liveTransaction.setAnimationTransaction(); - } - - event->readyToExecute(); - - liveTransaction.apply(t.synchronous()); - - ALOGV("Ended Transaction"); - - return status; -} - -status_t Replayer::doSurfaceTransaction( - SurfaceComposerClient::Transaction& transaction, - const SurfaceChanges& surfaceChanges) { - status_t status = NO_ERROR; - - for (const SurfaceChange& change : surfaceChanges) { - std::unique_lock lock(mLayerLock); - if (mLayers[change.id()] == nullptr) { - mLayerCond.wait(lock, [&] { return (mLayers[change.id()] != nullptr); }); - } - - switch (change.SurfaceChange_case()) { - case SurfaceChange::SurfaceChangeCase::kPosition: - setPosition(transaction, change.id(), change.position()); - break; - case SurfaceChange::SurfaceChangeCase::kSize: - setSize(transaction, change.id(), change.size()); - break; - case SurfaceChange::SurfaceChangeCase::kAlpha: - setAlpha(transaction, change.id(), change.alpha()); - break; - case SurfaceChange::SurfaceChangeCase::kLayer: - setLayer(transaction, change.id(), change.layer()); - break; - case SurfaceChange::SurfaceChangeCase::kCrop: - setCrop(transaction, change.id(), change.crop()); - break; - case SurfaceChange::SurfaceChangeCase::kCornerRadius: - setCornerRadius(transaction, change.id(), change.corner_radius()); - break; - case SurfaceChange::SurfaceChangeCase::kMatrix: - setMatrix(transaction, change.id(), change.matrix()); - break; - case SurfaceChange::SurfaceChangeCase::kTransparentRegionHint: - setTransparentRegionHint(transaction, change.id(), - change.transparent_region_hint()); - break; - case SurfaceChange::SurfaceChangeCase::kLayerStack: - setLayerStack(transaction, change.id(), change.layer_stack()); - break; - case SurfaceChange::SurfaceChangeCase::kHiddenFlag: - setHiddenFlag(transaction, change.id(), change.hidden_flag()); - break; - case SurfaceChange::SurfaceChangeCase::kOpaqueFlag: - setOpaqueFlag(transaction, change.id(), change.opaque_flag()); - break; - case SurfaceChange::SurfaceChangeCase::kSecureFlag: - setSecureFlag(transaction, change.id(), change.secure_flag()); - break; - case SurfaceChange::SurfaceChangeCase::kReparent: - setReparentChange(transaction, change.id(), change.reparent()); - break; - case SurfaceChange::SurfaceChangeCase::kRelativeParent: - setRelativeParentChange(transaction, change.id(), change.relative_parent()); - break; - case SurfaceChange::SurfaceChangeCase::kShadowRadius: - setShadowRadiusChange(transaction, change.id(), change.shadow_radius()); - break; - case SurfaceChange::SurfaceChangeCase::kBlurRegions: - setBlurRegionsChange(transaction, change.id(), change.blur_regions()); - break; - default: - status = 1; - break; - } - - if (status != NO_ERROR) { - ALOGE("Unknown Transaction Code"); - return status; - } - } - return status; -} - -void Replayer::doDisplayTransaction(SurfaceComposerClient::Transaction& t, - const DisplayChanges& displayChanges) { - for (const DisplayChange& change : displayChanges) { - ALOGV("Doing display transaction"); - std::unique_lock lock(mDisplayLock); - if (mDisplays[change.id()] == nullptr) { - mDisplayCond.wait(lock, [&] { return (mDisplays[change.id()] != nullptr); }); - } - - switch (change.DisplayChange_case()) { - case DisplayChange::DisplayChangeCase::kSurface: - setDisplaySurface(t, change.id(), change.surface()); - break; - case DisplayChange::DisplayChangeCase::kLayerStack: - setDisplayLayerStack(t, change.id(), change.layer_stack()); - break; - case DisplayChange::DisplayChangeCase::kSize: - setDisplaySize(t, change.id(), change.size()); - break; - case DisplayChange::DisplayChangeCase::kProjection: - setDisplayProjection(t, change.id(), change.projection()); - break; - default: - break; - } - } -} - -void Replayer::setPosition(SurfaceComposerClient::Transaction& t, - layer_id id, const PositionChange& pc) { - ALOGV("Layer %d: Setting Position -- x=%f, y=%f", id, pc.x(), pc.y()); - t.setPosition(mLayers[id], pc.x(), pc.y()); -} - -void Replayer::setSize(SurfaceComposerClient::Transaction& t, - layer_id id, const SizeChange& sc) { - ALOGV("Layer %d: Setting Size -- w=%u, h=%u", id, sc.w(), sc.h()); -} - -void Replayer::setLayer(SurfaceComposerClient::Transaction& t, - layer_id id, const LayerChange& lc) { - ALOGV("Layer %d: Setting Layer -- layer=%d", id, lc.layer()); - t.setLayer(mLayers[id], lc.layer()); -} - -void Replayer::setAlpha(SurfaceComposerClient::Transaction& t, - layer_id id, const AlphaChange& ac) { - ALOGV("Layer %d: Setting Alpha -- alpha=%f", id, ac.alpha()); - t.setAlpha(mLayers[id], ac.alpha()); -} - -void Replayer::setCrop(SurfaceComposerClient::Transaction& t, - layer_id id, const CropChange& cc) { - ALOGV("Layer %d: Setting Crop -- left=%d, top=%d, right=%d, bottom=%d", id, - cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(), - cc.rectangle().bottom()); - - Rect r = Rect(cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(), - cc.rectangle().bottom()); - t.setCrop(mLayers[id], r); -} - -void Replayer::setCornerRadius(SurfaceComposerClient::Transaction& t, - layer_id id, const CornerRadiusChange& cc) { - ALOGV("Layer %d: Setting Corner Radius -- cornerRadius=%d", id, cc.corner_radius()); - - t.setCornerRadius(mLayers[id], cc.corner_radius()); -} - -void Replayer::setBackgroundBlurRadius(SurfaceComposerClient::Transaction& t, - layer_id id, const BackgroundBlurRadiusChange& cc) { - ALOGV("Layer %d: Setting Background Blur Radius -- backgroundBlurRadius=%d", id, - cc.background_blur_radius()); - - t.setBackgroundBlurRadius(mLayers[id], cc.background_blur_radius()); -} - -void Replayer::setMatrix(SurfaceComposerClient::Transaction& t, - layer_id id, const MatrixChange& mc) { - ALOGV("Layer %d: Setting Matrix -- dsdx=%f, dtdx=%f, dsdy=%f, dtdy=%f", id, mc.dsdx(), - mc.dtdx(), mc.dsdy(), mc.dtdy()); - t.setMatrix(mLayers[id], mc.dsdx(), mc.dtdx(), mc.dsdy(), mc.dtdy()); -} - -void Replayer::setTransparentRegionHint(SurfaceComposerClient::Transaction& t, - layer_id id, const TransparentRegionHintChange& trhc) { - ALOGV("Setting Transparent Region Hint"); - Region re = Region(); - - for (const auto& r : trhc.region()) { - Rect rect = Rect(r.left(), r.top(), r.right(), r.bottom()); - re.merge(rect); - } - - t.setTransparentRegionHint(mLayers[id], re); -} - -void Replayer::setLayerStack(SurfaceComposerClient::Transaction& t, - layer_id id, const LayerStackChange& lsc) { - ALOGV("Layer %d: Setting LayerStack -- layer_stack=%d", id, lsc.layer_stack()); - t.setLayerStack(mLayers[id], ui::LayerStack::fromValue(lsc.layer_stack())); -} - -void Replayer::setHiddenFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const HiddenFlagChange& hfc) { - ALOGV("Layer %d: Setting Hidden Flag -- hidden_flag=%d", id, hfc.hidden_flag()); - layer_id flag = hfc.hidden_flag() ? layer_state_t::eLayerHidden : 0; - - t.setFlags(mLayers[id], flag, layer_state_t::eLayerHidden); -} - -void Replayer::setOpaqueFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const OpaqueFlagChange& ofc) { - ALOGV("Layer %d: Setting Opaque Flag -- opaque_flag=%d", id, ofc.opaque_flag()); - layer_id flag = ofc.opaque_flag() ? layer_state_t::eLayerOpaque : 0; - - t.setFlags(mLayers[id], flag, layer_state_t::eLayerOpaque); -} - -void Replayer::setSecureFlag(SurfaceComposerClient::Transaction& t, - layer_id id, const SecureFlagChange& sfc) { - ALOGV("Layer %d: Setting Secure Flag -- secure_flag=%d", id, sfc.secure_flag()); - layer_id flag = sfc.secure_flag() ? layer_state_t::eLayerSecure : 0; - - t.setFlags(mLayers[id], flag, layer_state_t::eLayerSecure); -} - -void Replayer::setDisplaySurface(SurfaceComposerClient::Transaction& t, - display_id id, const DispSurfaceChange& /*dsc*/) { - sp outProducer; - sp outConsumer; - BufferQueue::createBufferQueue(&outProducer, &outConsumer); - - t.setDisplaySurface(mDisplays[id], outProducer); -} - -void Replayer::setDisplayLayerStack(SurfaceComposerClient::Transaction& t, - display_id id, const LayerStackChange& lsc) { - t.setDisplayLayerStack(mDisplays[id], ui::LayerStack::fromValue(lsc.layer_stack())); -} - -void Replayer::setDisplaySize(SurfaceComposerClient::Transaction& t, - display_id id, const SizeChange& sc) { - t.setDisplaySize(mDisplays[id], sc.w(), sc.h()); -} - -void Replayer::setDisplayProjection(SurfaceComposerClient::Transaction& t, - display_id id, const ProjectionChange& pc) { - Rect viewport = Rect(pc.viewport().left(), pc.viewport().top(), pc.viewport().right(), - pc.viewport().bottom()); - Rect frame = Rect(pc.frame().left(), pc.frame().top(), pc.frame().right(), pc.frame().bottom()); - - t.setDisplayProjection(mDisplays[id], ui::toRotation(pc.orientation()), viewport, frame); -} - -status_t Replayer::createSurfaceControl( - const SurfaceCreation& create, const std::shared_ptr& event) { - event->readyToExecute(); - - ALOGV("Creating Surface Control: ID: %d", create.id()); - sp surfaceControl = mComposerClient->createSurface( - String8(create.name().c_str()), create.w(), create.h(), PIXEL_FORMAT_RGBA_8888, 0); - - if (surfaceControl == nullptr) { - ALOGE("CreateSurfaceControl: unable to create surface control"); - return BAD_VALUE; - } - - std::lock_guard lock1(mLayerLock); - auto& layer = mLayers[create.id()]; - layer = surfaceControl; - - mColors[create.id()] = HSV(rand() % 360, 1, 1); - - mLayerCond.notify_all(); - - std::lock_guard lock2(mBufferQueueSchedulerLock); - if (mBufferQueueSchedulers.count(create.id()) != 0) { - mBufferQueueSchedulers[create.id()]->setSurfaceControl( - mLayers[create.id()], mColors[create.id()]); - } - - return NO_ERROR; -} - -status_t Replayer::injectVSyncEvent( - const VSyncEvent& vSyncEvent, const std::shared_ptr& event) { - ALOGV("Injecting VSync Event"); - - event->readyToExecute(); - - SurfaceComposerClient::injectVSync(vSyncEvent.when()); - - return NO_ERROR; -} - -void Replayer::createDisplay(const DisplayCreation& create, const std::shared_ptr& event) { - ALOGV("Creating display"); - event->readyToExecute(); - - std::lock_guard lock(mDisplayLock); - sp display = SurfaceComposerClient::createDisplay( - String8(create.name().c_str()), create.is_secure()); - mDisplays[create.id()] = display; - - mDisplayCond.notify_all(); - - ALOGV("Done creating display"); -} - -void Replayer::deleteDisplay(const DisplayDeletion& delete_, const std::shared_ptr& event) { - ALOGV("Delete display"); - event->readyToExecute(); - - std::lock_guard lock(mDisplayLock); - SurfaceComposerClient::destroyDisplay(mDisplays[delete_.id()]); - mDisplays.erase(delete_.id()); -} - -void Replayer::updatePowerMode(const PowerModeUpdate& pmu, const std::shared_ptr& event) { - ALOGV("Updating power mode"); - event->readyToExecute(); - SurfaceComposerClient::setDisplayPowerMode(mDisplays[pmu.id()], pmu.mode()); -} - -void Replayer::waitUntilTimestamp(int64_t timestamp) { - ALOGV("Waiting for %lld nanoseconds...", static_cast(timestamp - mCurrentTime)); - std::this_thread::sleep_for(std::chrono::nanoseconds(timestamp - mCurrentTime)); -} - -status_t Replayer::loadSurfaceComposerClient() { - mComposerClient = new SurfaceComposerClient; - return mComposerClient->initCheck(); -} - -void Replayer::setReparentChange(SurfaceComposerClient::Transaction& t, - layer_id id, const ReparentChange& c) { - sp newSurfaceControl = nullptr; - if (mLayers.count(c.parent_id()) != 0 && mLayers[c.parent_id()] != nullptr) { - newSurfaceControl = mLayers[c.parent_id()]; - } - t.reparent(mLayers[id], newSurfaceControl); -} - -void Replayer::setRelativeParentChange(SurfaceComposerClient::Transaction& t, - layer_id id, const RelativeParentChange& c) { - if (mLayers.count(c.relative_parent_id()) == 0 || mLayers[c.relative_parent_id()] == nullptr) { - ALOGE("Layer %d not found in set relative parent transaction", c.relative_parent_id()); - return; - } - t.setRelativeLayer(mLayers[id], mLayers[c.relative_parent_id()], c.z()); -} - -void Replayer::setShadowRadiusChange(SurfaceComposerClient::Transaction& t, - layer_id id, const ShadowRadiusChange& c) { - t.setShadowRadius(mLayers[id], c.radius()); -} - -void Replayer::setBlurRegionsChange(SurfaceComposerClient::Transaction& t, - layer_id id, const BlurRegionsChange& c) { - std::vector regions; - for(size_t i=0; i < c.blur_regions_size(); i++) { - auto protoRegion = c.blur_regions(i); - regions.push_back(BlurRegion{ - .blurRadius = protoRegion.blur_radius(), - .alpha = protoRegion.alpha(), - .cornerRadiusTL = protoRegion.corner_radius_tl(), - .cornerRadiusTR = protoRegion.corner_radius_tr(), - .cornerRadiusBL = protoRegion.corner_radius_bl(), - .cornerRadiusBR = protoRegion.corner_radius_br(), - .left = protoRegion.left(), - .top = protoRegion.top(), - .right = protoRegion.right(), - .bottom = protoRegion.bottom() - }); - } - t.setBlurRegions(mLayers[id], regions); -} -- GitLab From cfbee5304f8fa7210e6f0816ca2ef2b6ab22c689 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Fri, 10 May 2024 13:41:35 -0700 Subject: [PATCH 331/465] Move ADISPLAY_ID_ definitions into LogicalDisplayId These will eventually replace the existing definitions as follows: ADISPLAY_ID_NONE -->> LogicalDisplayId::INVALID ADISPLAY_ID_DEFAULT -->> LogicalDisplayId::DEFAULT We are keeping the old definitions for now, to reduce the merge conflicts. These will be removed in subsequent CLs, after the frameworks/base and other repository patches go in. The constant "NONE" was renamed to "INVALID" to make it consistent with the Java definitions. Bug: 339106983 Test: m checkinput Change-Id: I0274be345159c85cb51fcea743d8acd3d298cd07 --- include/input/DisplayViewport.h | 6 +- include/input/Input.h | 2 +- include/input/InputDevice.h | 2 +- include/input/InputEventBuilders.h | 4 +- libs/gui/include/gui/DisplayInfo.h | 2 +- libs/gui/include/gui/WindowInfo.h | 2 +- libs/gui/tests/EndToEndNativeInputTest.cpp | 8 +- libs/input/Input.cpp | 8 +- libs/input/InputDevice.cpp | 2 +- libs/input/KeyCharacterMap.cpp | 7 +- libs/input/tests/InputEvent_test.cpp | 6 +- ...tPublisherAndConsumerNoResampling_test.cpp | 28 +- .../tests/InputPublisherAndConsumer_test.cpp | 28 +- libs/input/tests/MotionPredictor_test.cpp | 7 +- libs/input/tests/TouchResampling_test.cpp | 10 +- libs/input/tests/VelocityTracker_test.cpp | 4 +- libs/input/tests/VerifiedInputEvent_test.cpp | 4 +- libs/ui/include/ui/LogicalDisplayId.h | 14 +- services/inputflinger/InputReaderBase.cpp | 2 +- .../inputflinger/PointerChoreographer.cpp | 8 +- services/inputflinger/PointerChoreographer.h | 2 +- .../benchmarks/InputDispatcher_benchmarks.cpp | 6 +- .../dispatcher/InputDispatcher.cpp | 28 +- services/inputflinger/dispatcher/InputState.h | 4 +- .../inputflinger/include/InputReaderBase.h | 4 +- services/inputflinger/include/NotifyArgs.h | 4 +- .../inputflinger/include/NotifyArgsBuilders.h | 4 +- services/inputflinger/reader/InputDevice.cpp | 3 +- .../mapper/CapturedTouchpadEventConverter.cpp | 2 +- .../reader/mapper/CursorInputMapper.cpp | 9 +- .../reader/mapper/CursorInputMapper.h | 2 +- .../reader/mapper/JoystickInputMapper.cpp | 2 +- .../reader/mapper/KeyboardInputMapper.cpp | 2 +- .../mapper/RotaryEncoderInputMapper.cpp | 2 +- .../reader/mapper/TouchInputMapper.cpp | 10 +- .../reader/mapper/TouchInputMapper.h | 4 +- .../reader/mapper/TouchpadInputMapper.cpp | 5 +- .../reader/mapper/TouchpadInputMapper.h | 2 +- .../tests/CursorInputMapper_test.cpp | 5 +- .../tests/FakePointerController.cpp | 2 +- services/inputflinger/tests/FakeWindows.cpp | 8 +- services/inputflinger/tests/FakeWindows.h | 25 +- .../tests/GestureConverter_test.cpp | 237 +- .../InputDeviceMetricsCollector_test.cpp | 2 +- .../tests/InputDispatcher_test.cpp | 2484 +++++++++-------- .../tests/InputProcessorConverter_test.cpp | 2 +- .../tests/InputProcessor_test.cpp | 4 +- .../inputflinger/tests/InputReader_test.cpp | 29 +- .../inputflinger/tests/InputTracingTest.cpp | 2 +- .../tests/LatencyTracker_test.cpp | 2 +- .../tests/MultiTouchInputMapper_test.cpp | 2 +- .../tests/PointerChoreographer_test.cpp | 173 +- .../tests/PreferStylusOverTouch_test.cpp | 4 +- .../tests/TouchpadInputMapper_test.cpp | 2 +- .../tests/UnwantedInteractionBlocker_test.cpp | 6 +- .../tests/fuzzers/InputClassifierFuzzer.cpp | 2 +- 56 files changed, 1763 insertions(+), 1477 deletions(-) diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h index 97c2ab8451..56294dd91a 100644 --- a/include/input/DisplayViewport.h +++ b/include/input/DisplayViewport.h @@ -46,7 +46,7 @@ enum class ViewportType : int32_t { * See com.android.server.display.DisplayViewport. */ struct DisplayViewport { - ui::LogicalDisplayId displayId; // ADISPLAY_ID_NONE if invalid + ui::LogicalDisplayId displayId; ui::Rotation orientation; int32_t logicalLeft; int32_t logicalTop; @@ -66,7 +66,7 @@ struct DisplayViewport { ViewportType type; DisplayViewport() - : displayId(ui::ADISPLAY_ID_NONE), + : displayId(ui::LogicalDisplayId::INVALID), orientation(ui::ROTATION_0), logicalLeft(0), logicalTop(0), @@ -101,7 +101,7 @@ struct DisplayViewport { inline bool isValid() const { return displayId.isValid(); } void setNonDisplayViewport(int32_t width, int32_t height) { - displayId = ui::ADISPLAY_ID_NONE; + displayId = ui::LogicalDisplayId::INVALID; orientation = ui::ROTATION_0; logicalLeft = 0; logicalTop = 0; diff --git a/include/input/Input.h b/include/input/Input.h index e939145bbf..3ca9c19876 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -553,7 +553,7 @@ protected: int32_t mId; DeviceId mDeviceId; uint32_t mSource; - ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::INVALID}; std::array mHmac; }; diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 8783d9c192..7d8c19e702 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -366,7 +366,7 @@ private: int32_t mKeyboardType; std::shared_ptr mKeyCharacterMap; std::optional mUsiVersion; - ui::LogicalDisplayId mAssociatedDisplayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId mAssociatedDisplayId{ui::LogicalDisplayId::INVALID}; bool mEnabled; bool mHasVibrator; diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h index 837e70e114..25d35e9fe7 100644 --- a/include/input/InputEventBuilders.h +++ b/include/input/InputEventBuilders.h @@ -158,7 +158,7 @@ private: int32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT}; int32_t mActionButton{0}; int32_t mButtonState{0}; int32_t mFlags{0}; @@ -247,7 +247,7 @@ private: uint32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT}; uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mFlags{0}; int32_t mKeyCode{AKEYCODE_UNKNOWN}; diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h index 7282b807a4..7094658379 100644 --- a/libs/gui/include/gui/DisplayInfo.h +++ b/libs/gui/include/gui/DisplayInfo.h @@ -29,7 +29,7 @@ namespace android::gui { * This should only be used by InputFlinger to support raw coordinates in logical display space. */ struct DisplayInfo : public Parcelable { - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID; // Logical display dimensions. int32_t logicalWidth = 0; diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h index 3ea3f67bd8..eb3be5588a 100644 --- a/libs/gui/include/gui/WindowInfo.h +++ b/libs/gui/include/gui/WindowInfo.h @@ -234,7 +234,7 @@ struct WindowInfo : public Parcelable { Uid ownerUid = Uid::INVALID; std::string packageName; ftl::Flags inputConfig; - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID; InputApplicationInfo applicationInfo; bool replaceTouchableRegionWithCrop = false; wp touchableRegionCropHandle; diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index c0e79655f8..45e33902c7 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -59,8 +59,6 @@ using android::gui::FocusRequest; using android::gui::InputApplicationInfo; using android::gui::TouchOcclusionMode; using android::gui::WindowInfo; -using android::ui::ADISPLAY_ID_DEFAULT; -using android::ui::ADISPLAY_ID_NONE; namespace android { namespace { @@ -313,7 +311,7 @@ public: reportedListener->wait(); } - void requestFocus(ui::LogicalDisplayId displayId = ADISPLAY_ID_DEFAULT) { + void requestFocus(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT) { SurfaceComposerClient::Transaction t; FocusRequest request; request.token = mInputInfo.token; @@ -438,7 +436,7 @@ void injectTapOnDisplay(int x, int y, ui::LogicalDisplayId displayId) { } void injectTap(int x, int y) { - injectTapOnDisplay(x, y, ADISPLAY_ID_DEFAULT); + injectTapOnDisplay(x, y, ui::LogicalDisplayId::DEFAULT); } void injectKeyOnDisplay(uint32_t keycode, ui::LogicalDisplayId displayId) { @@ -451,7 +449,7 @@ void injectKeyOnDisplay(uint32_t keycode, ui::LogicalDisplayId displayId) { } void injectKey(uint32_t keycode) { - injectKeyOnDisplay(keycode, ADISPLAY_ID_NONE); + injectKeyOnDisplay(keycode, ui::LogicalDisplayId::INVALID); } TEST_F(InputSurfacesTest, can_receive_input) { diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 1178d02510..ee121d53fe 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -1198,7 +1198,7 @@ std::ostream& operator<<(std::ostream& out, const MotionEvent& event) { void FocusEvent::initialize(int32_t id, bool hasFocus) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ui::ADISPLAY_ID_NONE, INVALID_HMAC); + ui::LogicalDisplayId::INVALID, INVALID_HMAC); mHasFocus = hasFocus; } @@ -1211,7 +1211,7 @@ void FocusEvent::initialize(const FocusEvent& from) { void CaptureEvent::initialize(int32_t id, bool pointerCaptureEnabled) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ui::ADISPLAY_ID_NONE, INVALID_HMAC); + ui::LogicalDisplayId::INVALID, INVALID_HMAC); mPointerCaptureEnabled = pointerCaptureEnabled; } @@ -1224,7 +1224,7 @@ void CaptureEvent::initialize(const CaptureEvent& from) { void DragEvent::initialize(int32_t id, float x, float y, bool isExiting) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ui::ADISPLAY_ID_NONE, INVALID_HMAC); + ui::LogicalDisplayId::INVALID, INVALID_HMAC); mIsExiting = isExiting; mX = x; mY = y; @@ -1241,7 +1241,7 @@ void DragEvent::initialize(const DragEvent& from) { void TouchModeEvent::initialize(int32_t id, bool isInTouchMode) { InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN, - ui::ADISPLAY_ID_NONE, INVALID_HMAC); + ui::LogicalDisplayId::INVALID, INVALID_HMAC); mIsInTouchMode = isInTouchMode; } diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index 50239a1f9f..bc678103c2 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -169,7 +169,7 @@ std::string InputDeviceIdentifier::getCanonicalName() const { // --- InputDeviceInfo --- InputDeviceInfo::InputDeviceInfo() { - initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ui::ADISPLAY_ID_NONE); + initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ui::LogicalDisplayId::INVALID); } InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp index f75bf410f2..1cf5612d45 100644 --- a/libs/input/KeyCharacterMap.cpp +++ b/libs/input/KeyCharacterMap.cpp @@ -497,9 +497,10 @@ void KeyCharacterMap::addKey(Vector& outEvents, int32_t deviceId, int3 int32_t metaState, bool down, nsecs_t time) { outEvents.push(); KeyEvent& event = outEvents.editTop(); - event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, - INVALID_HMAC, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode, - 0, metaState, 0, time, time); + event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD, + ui::LogicalDisplayId::INVALID, INVALID_HMAC, + down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode, 0, metaState, + 0, time, time); } void KeyCharacterMap::addMetaKeys(Vector& outEvents, diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index cc2574de9a..704ea46fe9 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -27,7 +27,7 @@ namespace android { // Default display id. -static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; @@ -858,8 +858,8 @@ MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy); nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); MotionEvent event; - event.initialize(InputEvent::nextId(), /* deviceId */ 1, source, - /* displayId */ ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, action, + event.initialize(InputEvent::nextId(), /* deviceId */ 1, source, ui::LogicalDisplayId::DEFAULT, + INVALID_HMAC, action, /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, transform, /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp index 7ae1cd8444..70529bbd39 100644 --- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp @@ -55,7 +55,7 @@ struct PublishMotionArgs { const int32_t eventId; const int32_t deviceId = 1; const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; - const ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; + const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT; const int32_t actionButton = 0; const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; @@ -445,7 +445,7 @@ void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() { int32_t eventId = InputEvent::nextId(); constexpr int32_t deviceId = 1; constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD; - constexpr ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; + constexpr ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT; constexpr std::array hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; @@ -737,10 +737,10 @@ TEST_F(InputPublisherAndConsumerNoResamplingTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, - INVALID_HMAC, 0, 0, 0, 0, 0, 0, - MotionClassification::NONE, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0, + 0, 0, MotionClassification::NONE, identityTransform, 0, + 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; @@ -755,10 +755,10 @@ TEST_F(InputPublisherAndConsumerNoResamplingTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, - INVALID_HMAC, 0, 0, 0, 0, 0, 0, - MotionClassification::NONE, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0, + 0, 0, MotionClassification::NONE, identityTransform, 0, + 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; @@ -777,10 +777,10 @@ TEST_F(InputPublisherAndConsumerNoResamplingTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, - INVALID_HMAC, 0, 0, 0, 0, 0, 0, - MotionClassification::NONE, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0, + 0, 0, MotionClassification::NONE, identityTransform, 0, + 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index d0dbe2ad31..48512f7c6e 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -48,7 +48,7 @@ struct PublishMotionArgs { const int32_t eventId; const int32_t deviceId = 1; const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN; - const ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; + const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT; const int32_t actionButton = 0; const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; @@ -262,7 +262,7 @@ void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() { int32_t eventId = InputEvent::nextId(); constexpr int32_t deviceId = 1; constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD; - constexpr ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT; + constexpr ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT; constexpr std::array hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; @@ -622,10 +622,10 @@ TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZer ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, - INVALID_HMAC, 0, 0, 0, 0, 0, 0, - MotionClassification::NONE, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0, + 0, 0, MotionClassification::NONE, identityTransform, 0, + 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; @@ -639,10 +639,10 @@ TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessTha ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, - INVALID_HMAC, 0, 0, 0, 0, 0, 0, - MotionClassification::NONE, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0, + 0, 0, MotionClassification::NONE, identityTransform, 0, + 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; @@ -661,10 +661,10 @@ TEST_F(InputPublisherAndConsumerTest, ui::Transform identityTransform; status = - mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, ui::ADISPLAY_ID_DEFAULT, - INVALID_HMAC, 0, 0, 0, 0, 0, 0, - MotionClassification::NONE, identityTransform, 0, 0, - AMOTION_EVENT_INVALID_CURSOR_POSITION, + mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0, + 0, 0, MotionClassification::NONE, identityTransform, 0, + 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0, 0, pointerCount, pointerProperties, pointerCoords); ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE"; diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp index 1c9f0c7399..d077760757 100644 --- a/libs/input/tests/MotionPredictor_test.cpp +++ b/libs/input/tests/MotionPredictor_test.cpp @@ -58,9 +58,10 @@ static MotionEvent getMotionEvent(int32_t action, float x, float y, } ui::Transform identityTransform; - event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ui::ADISPLAY_ID_DEFAULT, - {0}, action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, - AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, + event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, + ui::LogicalDisplayId::DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0, + AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, + MotionClassification::NONE, identityTransform, /*xPrecision=*/0.1, /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540, identityTransform, /*downTime=*/100, eventTime.count(), pointerCount, diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 1694cadfbe..2dc9fdb7f1 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -84,11 +84,11 @@ status_t TouchResamplingTest::publishSimpleMotionEventWithCoords( ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)"; } return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1, - AINPUT_SOURCE_TOUCHSCREEN, - /*displayId=*/ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, - action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0, - AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, - identityTransform, /*xPrecision=*/0, /*yPrecision=*/0, + AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, + INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0, + /*edgeFlags=*/0, AMETA_NONE, /*buttonState=*/0, + MotionClassification::NONE, identityTransform, + /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTime, eventTime, properties.size(), properties.data(), diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp index b4c4e1286d..f50a3cdb99 100644 --- a/libs/input/tests/VelocityTracker_test.cpp +++ b/libs/input/tests/VelocityTracker_test.cpp @@ -33,7 +33,7 @@ using android::base::StringPrintf; namespace android { -constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; // default display id +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; // default display id constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually defined tests @@ -155,7 +155,7 @@ static std::vector createAxisScrollMotionEventStream( MotionEvent event; ui::Transform identityTransform; event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER, - ui::ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL, + ui::LogicalDisplayId::INVALID, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp index 40cfaaeb05..df5fe9d2d0 100644 --- a/libs/input/tests/VerifiedInputEvent_test.cpp +++ b/libs/input/tests/VerifiedInputEvent_test.cpp @@ -23,7 +23,7 @@ namespace android { static KeyEvent getKeyEventWithFlags(int32_t flags) { KeyEvent event; event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD, - ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags, AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1, /*downTime=*/1000, /*eventTime=*/2000); return event; @@ -44,7 +44,7 @@ static MotionEvent getMotionEventWithFlags(int32_t flags) { transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1}); ui::Transform identity; event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, - ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, flags, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, transform, /*xPrecision=*/0.1, /*yPrecision=*/0.2, diff --git a/libs/ui/include/ui/LogicalDisplayId.h b/libs/ui/include/ui/LogicalDisplayId.h index 3e504e7101..d745758f0c 100644 --- a/libs/ui/include/ui/LogicalDisplayId.h +++ b/libs/ui/include/ui/LogicalDisplayId.h @@ -35,10 +35,20 @@ struct LogicalDisplayId : ftl::Constructible, constexpr bool isValid() const { return val() >= 0; } std::string toString() const { return std::to_string(val()); } + + static const LogicalDisplayId INVALID; + static const LogicalDisplayId DEFAULT; }; -constexpr LogicalDisplayId ADISPLAY_ID_NONE{-1}; -constexpr LogicalDisplayId ADISPLAY_ID_DEFAULT{0}; +constexpr inline LogicalDisplayId LogicalDisplayId::INVALID{-1}; +constexpr inline LogicalDisplayId LogicalDisplayId::DEFAULT{0}; + +/** + * Deprecated! Use LogicalDisplayId::INVALID / LogicalDisplayId::DEFAULT instead. + * TODO(b/339106983): remove these. + */ +[[deprecated]] constexpr LogicalDisplayId ADISPLAY_ID_NONE{-1}; +[[deprecated]] constexpr LogicalDisplayId ADISPLAY_ID_DEFAULT{0}; inline std::ostream& operator<<(std::ostream& stream, LogicalDisplayId displayId) { return stream << displayId.val(); diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp index 29b487f1d2..6b2627ce96 100644 --- a/services/inputflinger/InputReaderBase.cpp +++ b/services/inputflinger/InputReaderBase.cpp @@ -68,7 +68,7 @@ std::optional InputReaderConfiguration::getDisplayViewportByTyp if (currentViewport.type == type) { if (!result || (type == ViewportType::INTERNAL && - currentViewport.displayId == ui::ADISPLAY_ID_DEFAULT)) { + currentViewport.displayId == ui::LogicalDisplayId::DEFAULT)) { result = std::make_optional(currentViewport); } count++; diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index d6c6f938d4..8a1eed603f 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -101,8 +101,8 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, }), mNextListener(listener), mPolicy(policy), - mDefaultMouseDisplayId(ui::ADISPLAY_ID_DEFAULT), - mNotifiedPointerDisplayId(ui::ADISPLAY_ID_NONE), + mDefaultMouseDisplayId(ui::LogicalDisplayId::DEFAULT), + mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID), mShowTouchesEnabled(false), mStylusPointerIconEnabled(false) {} @@ -236,7 +236,7 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo } void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) { - if (args.displayId == ui::ADISPLAY_ID_NONE) { + if (args.displayId == ui::LogicalDisplayId::INVALID) { return; } @@ -557,7 +557,7 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo PointerChoreographer::PointerDisplayChange PointerChoreographer::calculatePointerDisplayChangeToNotify() { - ui::LogicalDisplayId displayIdToNotify = ui::ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayIdToNotify = ui::LogicalDisplayId::INVALID; FloatPoint cursorPosition = {0, 0}; if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId); it != mMousePointersByDisplay.end()) { diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index fda5f5231e..12316c0427 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -57,7 +57,7 @@ public: virtual void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) = 0; virtual void setDisplayViewports(const std::vector& viewports) = 0; virtual std::optional getViewportForPointerDevice( - ui::LogicalDisplayId associatedDisplayId = ui::ADISPLAY_ID_NONE) = 0; + ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0; virtual FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0; virtual void setShowTouchesEnabled(bool enabled) = 0; virtual void setStylusPointerIconEnabled(bool enabled) = 0; diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp index 66e4a297dd..96c8640e2f 100644 --- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp +++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp @@ -37,7 +37,7 @@ namespace { constexpr DeviceId DEVICE_ID = 1; // An arbitrary display id -constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s; @@ -62,7 +62,7 @@ static MotionEvent generateMotionEvent() { ui::Transform identityTransform; MotionEvent event; event.initialize(IInputConstants::INVALID_INPUT_EVENT_ID, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, - ui::ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, + ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, identityTransform, /* xPrecision */ 0, @@ -88,7 +88,7 @@ static NotifyMotionArgs generateMotionArgs() { const nsecs_t currentTime = now(); // Define a valid motion event. NotifyMotionArgs args(IInputConstants::INVALID_INPUT_EVENT_ID, currentTime, currentTime, - DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ui::ADISPLAY_ID_DEFAULT, + DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN, /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8e0da1470e..dae2b6119c 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -918,9 +918,9 @@ InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy, mDispatchFrozen(false), mInputFilterEnabled(false), mMaximumObscuringOpacityForTouch(1.0f), - mFocusedDisplayId(ui::ADISPLAY_ID_DEFAULT), + mFocusedDisplayId(ui::LogicalDisplayId::DEFAULT), mWindowTokenWithPointerCapture(nullptr), - mAwaitedApplicationDisplayId(ui::ADISPLAY_ID_NONE), + mAwaitedApplicationDisplayId(ui::LogicalDisplayId::INVALID), mLatencyAggregator(), mLatencyTracker(&mLatencyAggregator) { mLooper = sp::make(false); @@ -2201,7 +2201,7 @@ void InputDispatcher::resetNoFocusedWindowTimeoutLocked() { * Focused display is the display that the user most recently interacted with. */ ui::LogicalDisplayId InputDispatcher::getTargetDisplayId(const EventEntry& entry) { - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID}; switch (entry.type) { case EventEntry::Type::KEY: { const KeyEntry& keyEntry = static_cast(entry); @@ -2221,10 +2221,10 @@ ui::LogicalDisplayId InputDispatcher::getTargetDisplayId(const EventEntry& entry case EventEntry::Type::SENSOR: case EventEntry::Type::DRAG: { ALOGE("%s events do not have a target display", ftl::enum_string(entry.type).c_str()); - return ui::ADISPLAY_ID_NONE; + return ui::LogicalDisplayId::INVALID; } } - return displayId == ui::ADISPLAY_ID_NONE ? mFocusedDisplayId : displayId; + return displayId == ui::LogicalDisplayId::INVALID ? mFocusedDisplayId : displayId; } bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime, @@ -2829,7 +2829,7 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( // Save changes unless the action was scroll in which case the temporary touch // state was only valid for this one action. if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) { - if (displayId >= ui::ADISPLAY_ID_DEFAULT) { + if (displayId >= ui::LogicalDisplayId::DEFAULT) { tempTouchState.clearWindowsWithoutPointers(); mTouchStatesByDisplay[displayId] = tempTouchState; } else { @@ -4862,8 +4862,8 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER); // If a pointer event has no displayId specified, inject it to the default display. const ui::LogicalDisplayId displayId = - isPointerEvent && (event->getDisplayId() == ui::ADISPLAY_ID_NONE) - ? ui::ADISPLAY_ID_DEFAULT + isPointerEvent && (event->getDisplayId() == ui::LogicalDisplayId::INVALID) + ? ui::LogicalDisplayId::DEFAULT : event->getDisplayId(); int32_t flags = motionEvent.getFlags(); @@ -4890,9 +4890,9 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev // events for consistency and print an error. An inconsistent event sent from // InputFilter could cause a crash in the later stages of dispatching pipeline. auto [it, _] = - mInputFilterVerifiersByDisplay - .try_emplace(displayId, - StringPrintf("Injection on %" PRId32, displayId)); + mInputFilterVerifiersByDisplay.try_emplace(displayId, + std::string("Injection on ") + + displayId.toString()); InputVerifier& verifier = it->second; Result result = @@ -5523,7 +5523,7 @@ void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) { options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, "The display which contains this window no longer has focus.", traceContext.getTracker()); - options.displayId = ui::ADISPLAY_ID_NONE; + options.displayId = ui::LogicalDisplayId::INVALID; synthesizeCancelationEventsForWindowLocked(windowHandle, options); } mFocusedDisplayId = displayId; @@ -5675,7 +5675,7 @@ InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp& token) } } } - return std::make_tuple(nullptr, nullptr, ui::ADISPLAY_ID_DEFAULT); + return std::make_tuple(nullptr, nullptr, ui::LogicalDisplayId::DEFAULT); } bool InputDispatcher::transferTouchGesture(const sp& fromToken, const sp& toToken, @@ -6125,7 +6125,7 @@ Result> InputDispatcher::createInputMonitor( { // acquire lock std::scoped_lock _l(mLock); - if (displayId < ui::ADISPLAY_ID_DEFAULT) { + if (displayId < ui::LogicalDisplayId::DEFAULT) { return base::Error(BAD_VALUE) << "Attempted to create input monitor with name " << name << " without a specified display."; } diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h index ab83ef911d..2808ba71bd 100644 --- a/services/inputflinger/dispatcher/InputState.h +++ b/services/inputflinger/dispatcher/InputState.h @@ -90,7 +90,7 @@ private: struct KeyMemento { DeviceId deviceId; uint32_t source; - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID}; int32_t keyCode; int32_t scanCode; int32_t metaState; @@ -102,7 +102,7 @@ private: struct MotionMemento { DeviceId deviceId; uint32_t source; - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID}; int32_t flags; float xPrecision; float yPrecision; diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index e5c3aa08d1..c62fc7d98e 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -243,7 +243,7 @@ struct InputReaderConfiguration { InputReaderConfiguration() : virtualKeyQuietTime(0), - defaultPointerDisplayId(ui::ADISPLAY_ID_DEFAULT), + defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT), mousePointerSpeed(0), displaysWithMousePointerAccelerationDisabled(), pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, @@ -472,7 +472,7 @@ public: * be used as the range of possible values for pointing devices, like mice and touchpads. */ virtual std::optional getPointerViewportForAssociatedDisplay( - ui::LogicalDisplayId associatedDisplayId = ui::ADISPLAY_ID_NONE) = 0; + ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0; }; } // namespace android diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h index 865f3d0302..db417cf830 100644 --- a/services/inputflinger/include/NotifyArgs.h +++ b/services/inputflinger/include/NotifyArgs.h @@ -61,7 +61,7 @@ struct NotifyKeyArgs { int32_t deviceId; uint32_t source; - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID}; uint32_t policyFlags; int32_t action; int32_t flags; @@ -91,7 +91,7 @@ struct NotifyMotionArgs { int32_t deviceId; uint32_t source; - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID}; uint32_t policyFlags; int32_t action; int32_t actionButton; diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h index 1ba0cfd20a..cae638f7bf 100644 --- a/services/inputflinger/include/NotifyArgsBuilders.h +++ b/services/inputflinger/include/NotifyArgsBuilders.h @@ -150,7 +150,7 @@ private: uint32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT}; uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mActionButton{0}; int32_t mButtonState{0}; @@ -229,7 +229,7 @@ private: uint32_t mSource; nsecs_t mDownTime; nsecs_t mEventTime; - ui::LogicalDisplayId mDisplayId{ui::ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT}; uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS; int32_t mFlags{0}; int32_t mKeyCode{AKEYCODE_UNKNOWN}; diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 15586e2c4b..956484c931 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -442,7 +442,8 @@ std::list InputDevice::updateExternalStylusState(const StylusState& InputDeviceInfo InputDevice::getDeviceInfo() { InputDeviceInfo outDeviceInfo; outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal, - mHasMic, getAssociatedDisplayId().value_or(ui::ADISPLAY_ID_NONE), + mHasMic, + getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID), {mShouldSmoothScroll}, isEnabled()); for_each_mapper( diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp index 21dec6ffa6..09a5f08a23 100644 --- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -278,7 +278,7 @@ NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs( LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(), "Mismatched coords and properties arrays."); return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, - ui::ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action, + ui::LogicalDisplayId::INVALID, /*policyFlags=*/POLICY_FLAG_WAKE, action, /*actionButton=*/actionButton, flags, mReaderContext.getGlobalMetaState(), mButtonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(), diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index d5fe0400b2..c67314dbcf 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -488,13 +488,13 @@ void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfigura if (mEnableNewMousePointerBallistics) { mNewPointerVelocityControl.setAccelerationEnabled( config.displaysWithMousePointerAccelerationDisabled.count( - mDisplayId.value_or(ui::ADISPLAY_ID_NONE)) == 0); + mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0); mNewPointerVelocityControl.setCurve( createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed)); } else { mOldPointerVelocityControl.setParameters( (config.displaysWithMousePointerAccelerationDisabled.count( - mDisplayId.value_or(ui::ADISPLAY_ID_NONE)) == 0) + mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0) ? config.pointerVelocityControlParameters : FLAT_VELOCITY_CONTROL_PARAMS); } @@ -506,7 +506,7 @@ void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfigura void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) { const bool isPointer = mParameters.mode == Parameters::Mode::POINTER; - mDisplayId = ui::ADISPLAY_ID_NONE; + mDisplayId = ui::LogicalDisplayId::INVALID; std::optional resolvedViewport; if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) { // This InputDevice is associated with a viewport. @@ -518,7 +518,8 @@ void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfigurat // Always use DISPLAY_ID_NONE for mouse events. // PointerChoreographer will make it target the correct the displayId later. resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); - mDisplayId = resolvedViewport ? std::make_optional(ui::ADISPLAY_ID_NONE) : std::nullopt; + mDisplayId = + resolvedViewport ? std::make_optional(ui::LogicalDisplayId::INVALID) : std::nullopt; } mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) || diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index e45105ad3a..75ca9c00a8 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -113,7 +113,7 @@ private: SimpleVelocityControl mWheelYVelocityControl; // The display that events generated by this mapper should target. This can be set to - // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. + // LogicalDisplayId::INVALID to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. std::optional mDisplayId; ui::Rotation mOrientation{ui::ROTATION_0}; diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 7fcff951b0..5ce4d30bb3 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -341,7 +341,7 @@ std::list JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, // button will likely wake the device. // TODO: Use the input device configuration to control this behavior more finely. uint32_t policyFlags = 0; - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID; if (getDeviceContext().getAssociatedViewport()) { displayId = getDeviceContext().getAssociatedViewport()->displayId; } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index be8d183215..21245555a2 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -114,7 +114,7 @@ ui::LogicalDisplayId KeyboardInputMapper::getDisplayId() { if (mViewport) { return mViewport->displayId; } - return ui::ADISPLAY_ID_NONE; + return ui::LogicalDisplayId::INVALID; } std::optional KeyboardInputMapper::getKeyboardLayoutInfo() const { diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index a3206c2844..0ddbc06890 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -125,7 +125,7 @@ std::list RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readT if (scrolled) { int32_t metaState = getContext()->getGlobalMetaState(); // This is not a pointer, so it's not associated with a display. - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE; + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID; if (mOrientation == ui::ROTATION_180) { scroll = -scroll; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 489bea8959..8b4b691a45 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2646,7 +2646,7 @@ std::list TouchInputMapper::dispatchPointerGestures(nsecs_t when, ns PointerCoords pointerCoords; pointerCoords.clear(); out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, ui::ADISPLAY_ID_NONE, policyFlags, + mSource, ui::LogicalDisplayId::INVALID, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState, buttonState, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, @@ -3477,7 +3477,8 @@ std::list TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs hovering = false; } - return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, ui::ADISPLAY_ID_NONE); + return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, + ui::LogicalDisplayId::INVALID); } std::list TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime, @@ -3684,7 +3685,8 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( source |= AINPUT_SOURCE_BLUETOOTH_STYLUS; } - const ui::LogicalDisplayId displayId = getAssociatedDisplayId().value_or(ui::ADISPLAY_ID_NONE); + const ui::LogicalDisplayId displayId = + getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID); float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION; @@ -3961,7 +3963,7 @@ bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, std::optional TouchInputMapper::getAssociatedDisplayId() { if (mParameters.hasAssociatedDisplay) { if (mDeviceMode == DeviceMode::POINTER) { - return ui::ADISPLAY_ID_NONE; + return ui::LogicalDisplayId::INVALID; } else { return std::make_optional(mViewport.displayId); } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index 0c9ffa24d0..b24f2ff8da 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -705,7 +705,7 @@ private: // Values reported for the last pointer event. uint32_t source; - ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_NONE}; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID}; float lastCursorX; float lastCursorY; @@ -718,7 +718,7 @@ private: hovering = false; downTime = 0; source = 0; - displayId = ui::ADISPLAY_ID_NONE; + displayId = ui::LogicalDisplayId::INVALID; lastCursorX = 0.f; lastCursorY = 0.f; } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index e157862f85..2a580c9e4f 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -322,7 +322,7 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, } if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) { - mDisplayId = ui::ADISPLAY_ID_NONE; + mDisplayId = ui::LogicalDisplayId::INVALID; std::optional resolvedViewport; std::optional boundsInLogicalDisplay; if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) { @@ -335,7 +335,8 @@ std::list TouchpadInputMapper::reconfigure(nsecs_t when, // Always use DISPLAY_ID_NONE for touchpad events. // PointerChoreographer will make it target the correct the displayId later. resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay(); - mDisplayId = resolvedViewport ? std::make_optional(ui::ADISPLAY_ID_NONE) : std::nullopt; + mDisplayId = resolvedViewport ? std::make_optional(ui::LogicalDisplayId::INVALID) + : std::nullopt; } mGestureConverter.setDisplayId(mDisplayId); diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index f27895fd9c..546fa5bb9c 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -107,7 +107,7 @@ private: std::set mPalmTrackingIds; // The display that events generated by this mapper should target. This can be set to - // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e. + // LogicalDisplayId::INVALID to target the focused display. If there is no display target (i.e. // std::nullopt), all events will be ignored. std::optional mDisplayId; diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index 72b5573047..83074ff899 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -50,7 +50,7 @@ constexpr auto BUTTON_PRESS = AMOTION_EVENT_ACTION_BUTTON_PRESS; constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION; -constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1}; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; @@ -909,7 +909,8 @@ TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) { EXPECT_THAT(args, ElementsAre(VariantWith( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), - WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ui::ADISPLAY_ID_NONE), + WithSource(AINPUT_SOURCE_MOUSE), + WithDisplayId(ui::LogicalDisplayId::INVALID), WithCoords(0.0f, 0.0f))))); } diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 731a28668f..2bb57b3e81 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -53,7 +53,7 @@ FloatPoint FakePointerController::getPosition() const { ui::LogicalDisplayId FakePointerController::getDisplayId() const { if (!mEnabled || !mDisplayId) { - return ui::ADISPLAY_ID_NONE; + return ui::LogicalDisplayId::INVALID; } return *mDisplayId; } diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp index c800d6ad4e..b116521155 100644 --- a/services/inputflinger/tests/FakeWindows.cpp +++ b/services/inputflinger/tests/FakeWindows.cpp @@ -152,7 +152,7 @@ void FakeInputReceiver::consumeFocusEvent(bool hasFocus, bool inTouchMode) { ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "Instead of FocusEvent, got " << *event; - ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) + ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; FocusEvent& focusEvent = static_cast(*event); @@ -165,7 +165,7 @@ void FakeInputReceiver::consumeCaptureEvent(bool hasCapture) { ASSERT_EQ(InputEventType::CAPTURE, event->getType()) << "Instead of CaptureEvent, got " << *event; - ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) + ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; const auto& captureEvent = static_cast(*event); @@ -177,7 +177,7 @@ void FakeInputReceiver::consumeDragEvent(bool isExiting, float x, float y) { ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event."; ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event; - EXPECT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) + EXPECT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; const auto& dragEvent = static_cast(*event); @@ -192,7 +192,7 @@ void FakeInputReceiver::consumeTouchModeEvent(bool inTouchMode) { ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType()) << "Instead of TouchModeEvent, got " << *event; - ASSERT_EQ(ui::ADISPLAY_ID_NONE, event->getDisplayId()) + ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId()) << mName.c_str() << ": event displayId should always be NONE."; const auto& touchModeEvent = static_cast(*event); EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode()); diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index 8a40337db4..36a8f0066e 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -261,22 +261,24 @@ public: } inline void consumeMotionCancel( - ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED))); } - inline void consumeMotionMove(ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionMove( + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, + int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } - inline void consumeMotionDown(ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionDown( + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, + int32_t expectedFlags = 0) { consumeAnyMotionDown(expectedDisplayId, expectedFlags); } @@ -292,7 +294,8 @@ public: } inline void consumeMotionPointerDown( - int32_t pointerIdx, ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + int32_t pointerIdx, + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, int32_t expectedFlags = 0) { const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); @@ -302,7 +305,8 @@ public: } inline void consumeMotionPointerUp( - int32_t pointerIdx, ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + int32_t pointerIdx, + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, int32_t expectedFlags = 0) { const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); @@ -311,15 +315,16 @@ public: WithFlags(expectedFlags))); } - inline void consumeMotionUp(ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionUp( + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, + int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDisplayId(expectedDisplayId), WithFlags(expectedFlags))); } inline void consumeMotionOutside( - ui::LogicalDisplayId expectedDisplayId = ui::ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, int32_t expectedFlags = 0) { consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithDisplayId(expectedDisplayId), diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 1132e9284c..2f9036f2b4 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -45,7 +45,6 @@ const auto TOUCHPAD_PALM_REJECTION_V2 = } // namespace -using android::ui::ADISPLAY_ID_DEFAULT; using testing::AllOf; using testing::Each; using testing::ElementsAre; @@ -93,7 +92,7 @@ protected: TEST_F(GestureConverterTest, Move) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -107,9 +106,9 @@ TEST_F(GestureConverterTest, Move) { WithRelativeMotion(-5, 10), WithButtonState(0), WithPressure(0.0f))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER. args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture); @@ -118,14 +117,14 @@ TEST_F(GestureConverterTest, Move) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0), WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Move_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -139,15 +138,15 @@ TEST_F(GestureConverterTest, Move_Rotated) { WithRelativeMotion(10, 5), WithButtonState(0), WithPressure(0.0f))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, ButtonsChange) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); // Press left and right buttons at once Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -170,9 +169,9 @@ TEST_F(GestureConverterTest, ButtonsChange) { WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Then release the left button Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -185,7 +184,7 @@ TEST_F(GestureConverterTest, ButtonsChange) { WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Finally release the right button Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -201,15 +200,15 @@ TEST_F(GestureConverterTest, ButtonsChange) { VariantWith( WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithButtonState(0), WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -223,13 +222,13 @@ TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) { VariantWith( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT)))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT)))); } TEST_F(GestureConverterTest, DragWithButton) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); // Press the button Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -246,9 +245,9 @@ TEST_F(GestureConverterTest, DragWithButton) { WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Move Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); @@ -258,7 +257,7 @@ TEST_F(GestureConverterTest, DragWithButton) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0), WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Release the button Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, @@ -274,16 +273,16 @@ TEST_F(GestureConverterTest, DragWithButton) { VariantWith( WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithButtonState(0), WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Scroll) { const nsecs_t downTime = 12345; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -303,7 +302,7 @@ TEST_F(GestureConverterTest, Scroll) { AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); @@ -314,7 +313,7 @@ TEST_F(GestureConverterTest, Scroll) { WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(ToolType::FINGER), WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); @@ -332,8 +331,9 @@ TEST_F(GestureConverterTest, Scroll) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_Rotated) { @@ -341,7 +341,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -360,7 +360,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { Each(VariantWith( AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5); args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); @@ -370,7 +370,7 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { WithGestureScrollDistance(0, 5, EPSILON), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, GESTURES_FLING_START); @@ -387,14 +387,15 @@ TEST_F(GestureConverterTest, Scroll_Rotated) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -412,13 +413,13 @@ TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) { ASSERT_THAT(args, ElementsAre(VariantWith( AllOf(WithMotionClassification(MotionClassification::NONE), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -443,7 +444,7 @@ TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/0); @@ -464,7 +465,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5, /*dy=*/5); @@ -491,7 +492,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { // only checks movement in one dimension. InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); @@ -502,7 +503,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { Each(VariantWith( AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. @@ -548,7 +549,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithPointerCount(3u), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX()); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX()); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX()); @@ -589,22 +590,24 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); converter.setOrientation(ui::ROTATION_90); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0, /* dy= */ 10); std::list args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); ASSERT_EQ(4u, args.size()); - ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ui::ADISPLAY_ID_DEFAULT)))); + ASSERT_THAT(args, + Each(VariantWith(WithDisplayId(ui::LogicalDisplayId::DEFAULT)))); // Three fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. @@ -648,7 +651,7 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { ASSERT_THAT(arg, AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15); @@ -674,13 +677,14 @@ TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) { WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))), VariantWith( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))))); - ASSERT_THAT(args, Each(VariantWith(WithDisplayId(ADISPLAY_ID_DEFAULT)))); + ASSERT_THAT(args, + Each(VariantWith(WithDisplayId(ui::LogicalDisplayId::DEFAULT)))); } TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 10, /* dy= */ 0); @@ -691,7 +695,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { Each(VariantWith( AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Four fake fingers should be created. We don't actually care where they are, so long as they // move appropriately. @@ -746,7 +750,7 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4), WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE), WithPointerCount(4u), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15); EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15); EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15); @@ -798,14 +802,15 @@ TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_Inwards) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); @@ -825,7 +830,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { AllOf(WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 0.8, GESTURES_ZOOM_UPDATE); @@ -837,7 +842,7 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0), WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); @@ -860,14 +865,15 @@ TEST_F(GestureConverterTest, Pinch_Inwards) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_Outwards) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_START); @@ -887,7 +893,7 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { AllOf(WithMotionClassification(MotionClassification::PINCH), WithGesturePinchScaleFactor(1.0f, EPSILON), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1.1, GESTURES_ZOOM_UPDATE); @@ -899,7 +905,7 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { WithGesturePinchScaleFactor(1.1f, EPSILON), WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0), WithPointerCount(2u), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1, GESTURES_ZOOM_END); @@ -922,14 +928,15 @@ TEST_F(GestureConverterTest, Pinch_Outwards) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); @@ -954,7 +961,7 @@ TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) { TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); @@ -981,7 +988,7 @@ TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) { TEST_F(GestureConverterTest, ResetWithButtonPressed) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT, @@ -1005,15 +1012,15 @@ TEST_F(GestureConverterTest, ResetWithButtonPressed) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithButtonState(0))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringScroll) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); @@ -1032,14 +1039,15 @@ TEST_F(GestureConverterTest, ResetDuringScroll) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0, /*dy=*/10); @@ -1073,14 +1081,15 @@ TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, ResetDuringPinch) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1, GESTURES_ZOOM_START); @@ -1105,14 +1114,15 @@ TEST_F(GestureConverterTest, ResetDuringPinch) { WithCoords(0, 0), WithMotionClassification(MotionClassification::NONE))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, FlingTapDown) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN); @@ -1122,14 +1132,15 @@ TEST_F(GestureConverterTest, FlingTapDown) { ASSERT_THAT(std::get(args.front()), AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), - WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithButtonState(0), WithPressure(0.0f), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); } TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); input_flags::enable_touchpad_fling_stop(true); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10); std::list args = @@ -1153,7 +1164,7 @@ TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) { ASSERT_THAT(args, Each(VariantWith( AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT), + WithDisplayId(ui::LogicalDisplayId::DEFAULT), WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE))))); } @@ -1161,7 +1172,7 @@ TEST_F(GestureConverterTest, Tap) { // Tap should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1198,17 +1209,17 @@ TEST_F(GestureConverterTest, Tap) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithButtonState(0), WithPressure(0.0f))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F(GestureConverterTest, Click) { // Click should produce button press/release events InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1235,10 +1246,10 @@ TEST_F(GestureConverterTest, Click) { WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, @@ -1257,10 +1268,10 @@ TEST_F(GestureConverterTest, Click) { AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithPressure(0.0f))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithButtonState(0), WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithButtonState(0), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); } TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, @@ -1273,7 +1284,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1302,7 +1313,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabledWithDelay, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1384,7 +1395,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0, /* vy= */ 0, GESTURES_FLING_TAP_DOWN); @@ -1411,10 +1422,10 @@ TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f))))); ASSERT_THAT(args, - Each(VariantWith(AllOf(WithCoords(0, 0), - WithRelativeMotion(0.f, 0.f), - WithToolType(ToolType::FINGER), - WithDisplayId(ui::ADISPLAY_ID_DEFAULT))))); + Each(VariantWith( + AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), + WithToolType(ToolType::FINGER), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* down= */ GESTURES_BUTTON_NONE, @@ -1428,18 +1439,20 @@ TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled, WithButtonState(0), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))), + WithPressure(1.0f), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))), VariantWith( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER), WithButtonState(0), - WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))), + WithPressure(0.0f), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))), VariantWith( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0), WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f), - WithDisplayId(ADISPLAY_ID_DEFAULT))))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))))); // Future taps should be re-enabled ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps()); @@ -1452,7 +1465,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick, InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10); std::list args = @@ -1468,7 +1481,7 @@ TEST_F_WITH_FLAGS(GestureConverterTest, KeypressCancelsHoverMove, const nsecs_t gestureStartTime = 1000; InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); - converter.setDisplayId(ui::ADISPLAY_ID_DEFAULT); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); // Start a move gesture at gestureStartTime Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10); diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp index 029414b23d..28699b8518 100644 --- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp +++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp @@ -63,7 +63,7 @@ InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID, uint32_t sources = TOUCHSCREEN | STYLUS) { auto info = InputDeviceInfo(); info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id), - "alias", /*isExternal=*/false, /*hasMic=*/false, ui::ADISPLAY_ID_NONE); + "alias", /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID); info.addSource(sources); return info; } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 184659df0d..8ad235f3a3 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -53,7 +53,6 @@ using android::gui::WindowInfo; using android::gui::WindowInfoHandle; using android::os::InputEventInjectionResult; using android::os::InputEventInjectionSync; -using android::ui::ADISPLAY_ID_DEFAULT; namespace android::inputdispatcher { @@ -73,7 +72,7 @@ static constexpr int32_t DEVICE_ID = DEFAULT_DEVICE_ID; static constexpr int32_t SECOND_DEVICE_ID = 2; // An arbitrary display id. -constexpr ui::LogicalDisplayId DISPLAY_ID = ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; constexpr ui::LogicalDisplayId SECOND_DISPLAY_ID = ui::LogicalDisplayId{1}; // Ensure common actions are interchangeable between keys and motions for convenience. @@ -129,9 +128,9 @@ using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID; static KeyEvent getTestKeyEvent() { KeyEvent event; - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, - INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, - ARBITRARY_TIME, ARBITRARY_TIME); + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, + AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); return event; } @@ -253,8 +252,8 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { KeyEvent event; // Rejects undefined key actions. - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, - INVALID_HMAC, + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + ui::LogicalDisplayId::INVALID, INVALID_HMAC, /*action=*/-1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, @@ -263,9 +262,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { << "Should reject key events with undefined action."; // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, - INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, - ARBITRARY_TIME, ARBITRARY_TIME); + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, + AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(InputEventInjectionResult::FAILED, mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE, 0ms, 0)) @@ -482,7 +481,7 @@ public: void consumeMotionPointerDown(int32_t pointerIdx) { int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - mInputReceiver.consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT, + mInputReceiver.consumeEvent(InputEventType::MOTION, action, ui::LogicalDisplayId::DEFAULT, /*expectedFlags=*/0); } @@ -500,7 +499,7 @@ private: static InputEventInjectionResult injectKey( InputDispatcher& dispatcher, int32_t action, int32_t repeatCount, - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE, + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID, InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT, std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT, bool allowKeyRepeat = true, std::optional targetUid = {}, @@ -522,15 +521,17 @@ static InputEventInjectionResult injectKey( static void assertInjectedKeyTimesOut(InputDispatcher& dispatcher) { InputEventInjectionResult result = - injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ui::ADISPLAY_ID_NONE, - InputEventInjectionSync::WAIT_FOR_RESULT, CONSUME_TIMEOUT_NO_EVENT_EXPECTED); + injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::INVALID, InputEventInjectionSync::WAIT_FOR_RESULT, + CONSUME_TIMEOUT_NO_EVENT_EXPECTED); if (result != InputEventInjectionResult::TIMED_OUT) { FAIL() << "Injection should have timed out, but got " << ftl::enum_string(result); } } static InputEventInjectionResult injectKeyDown( - InputDispatcher& dispatcher, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { + InputDispatcher& dispatcher, + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId); } @@ -538,14 +539,16 @@ static InputEventInjectionResult injectKeyDown( // sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it // has to be woken up to process the repeating key. static InputEventInjectionResult injectKeyDownNoRepeat( - InputDispatcher& dispatcher, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { + InputDispatcher& dispatcher, + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false); } static InputEventInjectionResult injectKeyUp( - InputDispatcher& dispatcher, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { + InputDispatcher& dispatcher, + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId); } @@ -596,8 +599,8 @@ static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location); } -static NotifyKeyArgs generateKeyArgs(int32_t action, - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { +static NotifyKeyArgs generateKeyArgs( + int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, @@ -608,7 +611,7 @@ static NotifyKeyArgs generateKeyArgs(int32_t action, } static NotifyKeyArgs generateSystemShortcutArgs( - int32_t action, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { + int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, @@ -619,7 +622,7 @@ static NotifyKeyArgs generateSystemShortcutArgs( } static NotifyKeyArgs generateAssistantKeyArgs( - int32_t action, ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE) { + int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); // Define a valid key event. NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, @@ -686,9 +689,9 @@ static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs( */ TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, - "Window that breaks its input channel", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, + "Window that breaks its input channel", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -699,16 +702,18 @@ TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) { TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } using InputDispatcherDeathTest = InputDispatcherTest; @@ -722,8 +727,9 @@ TEST_F(InputDispatcherDeathTest, DuplicateWindowInfosAbortDispatcher) { ScopedSilentDeath _silentDeath; std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); ASSERT_DEATH(mDispatcher->onWindowInfosChanged( {{*window->getInfo(), *window->getInfo()}, {}, 0, 0}), "Incorrect WindowInfosUpdate provided"); @@ -731,17 +737,19 @@ TEST_F(InputDispatcherDeathTest, DuplicateWindowInfosAbortDispatcher) { TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); // Inject a MotionEvent to an unknown display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::ADISPLAY_ID_NONE)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::INVALID)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } /** @@ -751,18 +759,19 @@ TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay */ TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } /** @@ -770,37 +779,40 @@ TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) { */ TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } // The foreground window should receive the first touch down event. TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) { std::shared_ptr application = std::make_shared(); - sp windowTop = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp windowTop = sp::make(application, mDispatcher, "Top", + ui::LogicalDisplayId::DEFAULT); sp windowSecond = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged( {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Top window should receive the touch down event. Second window should not receive anything. - windowTop->consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowTop->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); windowSecond->assertNoEvents(); } @@ -814,10 +826,12 @@ TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) { TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) { std::shared_ptr application = std::make_shared(); sp foregroundWindow = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); foregroundWindow->setDupTouchToWallpaper(true); sp wallpaperWindow = - sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", + ui::LogicalDisplayId::DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->onWindowInfosChanged( @@ -831,7 +845,7 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance // Both foreground window and its wallpaper should receive the touch down foregroundWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, @@ -841,13 +855,13 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); - wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Now the foreground window goes away, but the wallpaper stays mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0}); foregroundWindow->consumeMotionCancel(); // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too. - wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); } /** @@ -857,8 +871,8 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance */ TEST_F(InputDispatcherTest, CancelAfterPointer0Up) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); // First touch pointer down on right window @@ -897,30 +911,32 @@ TEST_F(InputDispatcherTest, CancelAfterPointer0Up) { TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) { std::shared_ptr application = std::make_shared(); sp foregroundWindow = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); foregroundWindow->setDupTouchToWallpaper(true); sp wallpaperWindow = - sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", + ui::LogicalDisplayId::DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->onWindowInfosChanged( {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Both foreground window and its wallpaper should receive the touch down foregroundWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 200})) + ui::LogicalDisplayId::DEFAULT, {110, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionMove(); - wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Wallpaper closes its channel, but the window remains. wallpaperWindow->destroyReceiver(); @@ -942,23 +958,23 @@ TEST_F(InputDispatcherTest, MultiDeviceDisappearingWindowWithWallpaperWindows) { std::shared_ptr application = std::make_shared(); sp leftForegroundWindow = sp::make(application, mDispatcher, "Left foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftForegroundWindow->setFrame(Rect(0, 0, 100, 100)); leftForegroundWindow->setDupTouchToWallpaper(true); sp leftWallpaperWindow = sp::make(application, mDispatcher, "Left wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100)); leftWallpaperWindow->setIsWallpaper(true); sp rightForegroundWindow = sp::make(application, mDispatcher, "Right foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightForegroundWindow->setFrame(Rect(100, 0, 200, 100)); rightForegroundWindow->setDupTouchToWallpaper(true); sp rightWallpaperWindow = sp::make(application, mDispatcher, "Right wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWallpaperWindow->setFrame(Rect(100, 0, 200, 100)); rightWallpaperWindow->setIsWallpaper(true); @@ -1026,35 +1042,35 @@ TEST_F(InputDispatcherTest, MultiDeviceSlipperyTouchWithWallpaperWindow) { std::shared_ptr application = std::make_shared(); sp leftForegroundWindow = sp::make(application, mDispatcher, "Left foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftForegroundWindow->setFrame(Rect(0, 0, 100, 100)); leftForegroundWindow->setDupTouchToWallpaper(true); sp leftWallpaperWindow = sp::make(application, mDispatcher, "Left wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100)); leftWallpaperWindow->setIsWallpaper(true); sp middleForegroundWindow = sp::make(application, mDispatcher, "Middle foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); middleForegroundWindow->setFrame(Rect(100, 0, 200, 100)); middleForegroundWindow->setDupTouchToWallpaper(true); middleForegroundWindow->setSlippery(true); sp middleWallpaperWindow = sp::make(application, mDispatcher, "Middle wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100)); middleWallpaperWindow->setIsWallpaper(true); sp rightForegroundWindow = sp::make(application, mDispatcher, "Right foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightForegroundWindow->setFrame(Rect(200, 0, 300, 100)); rightForegroundWindow->setDupTouchToWallpaper(true); sp rightWallpaperWindow = sp::make(application, mDispatcher, "Right wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100)); rightWallpaperWindow->setIsWallpaper(true); @@ -1132,34 +1148,34 @@ TEST_F(InputDispatcherTest, MultiDeviceTouchTransferWithWallpaperWindows) { std::shared_ptr application = std::make_shared(); sp leftForegroundWindow = sp::make(application, mDispatcher, "Left foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftForegroundWindow->setFrame(Rect(0, 0, 100, 100)); leftForegroundWindow->setDupTouchToWallpaper(true); sp leftWallpaperWindow = sp::make(application, mDispatcher, "Left wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100)); leftWallpaperWindow->setIsWallpaper(true); sp middleForegroundWindow = sp::make(application, mDispatcher, "Middle foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); middleForegroundWindow->setFrame(Rect(100, 0, 200, 100)); middleForegroundWindow->setDupTouchToWallpaper(true); sp middleWallpaperWindow = sp::make(application, mDispatcher, "Middle wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100)); middleWallpaperWindow->setIsWallpaper(true); sp rightForegroundWindow = sp::make(application, mDispatcher, "Right foreground window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightForegroundWindow->setFrame(Rect(200, 0, 300, 100)); rightForegroundWindow->setDupTouchToWallpaper(true); sp rightWallpaperWindow = sp::make(application, mDispatcher, "Right wallpaper window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100)); rightWallpaperWindow->setIsWallpaper(true); @@ -1239,12 +1255,14 @@ INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture, TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { std::shared_ptr application = std::make_shared(); sp foregroundWindow = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); foregroundWindow->setDupTouchToWallpaper(true); foregroundWindow->setPreventSplitting(GetParam()); sp wallpaperWindow = - sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", + ui::LogicalDisplayId::DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->onWindowInfosChanged( @@ -1252,13 +1270,13 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { // Touch down on top window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Both top window and its wallpaper should receive the touch down foregroundWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Second finger down on the top window const MotionEvent secondFingerDownEvent = @@ -1272,12 +1290,12 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1); - wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, + wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); const MotionEvent secondFingerUpEvent = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) @@ -1287,13 +1305,14 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; foregroundWindow->consumeMotionPointerUp(0); - wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionPointerUp(0, ui::LogicalDisplayId::DEFAULT, + EXPECTED_WALLPAPER_FLAGS); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER) .x(100) @@ -1301,8 +1320,8 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { .build(), INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + foregroundWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); + wallpaperWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); } /** @@ -1315,18 +1334,19 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { */ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); leftWindow->setDupTouchToWallpaper(true); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); rightWindow->setDupTouchToWallpaper(true); sp wallpaperWindow = - sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", + ui::LogicalDisplayId::DEFAULT); wallpaperWindow->setFrame(Rect(0, 0, 400, 200)); wallpaperWindow->setIsWallpaper(true); @@ -1338,13 +1358,13 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { // Touch down on left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Both foreground window and its wallpaper should receive the touch down leftWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Second finger down on the right window const MotionEvent secondFingerDownEvent = @@ -1360,8 +1380,8 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { leftWindow->consumeMotionMove(); // Since the touch is split, right window gets ACTION_DOWN - rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT, + rightWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Now, leftWindow, which received the first finger, disappears. @@ -1369,7 +1389,7 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0}); leftWindow->consumeMotionCancel(); // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too. - wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // The pointer that's still down on the right window moves, and goes to the right window only. // As far as the dispatcher's concerned though, both pointers are still present. @@ -1396,18 +1416,19 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) { */ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); leftWindow->setDupTouchToWallpaper(true); leftWindow->setSlippery(true); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); sp wallpaperWindow = - sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", + ui::LogicalDisplayId::DEFAULT); wallpaperWindow->setIsWallpaper(true); mDispatcher->onWindowInfosChanged( @@ -1418,23 +1439,23 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { // Touch down on left window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Both foreground window and its wallpaper should receive the touch down leftWindow->consumeMotionDown(); - wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Move to right window, the left window should receive cancel. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {201, 100})) + ui::LogicalDisplayId::DEFAULT, {201, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; leftWindow->consumeMotionCancel(); - rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); - wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + rightWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); } /** @@ -1455,14 +1476,14 @@ TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) { */ TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 200, 200)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); @@ -1533,8 +1554,8 @@ TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) { TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 300, 300)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -1595,12 +1616,12 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -1703,12 +1724,12 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -1792,8 +1813,8 @@ TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) { */ TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); // Only a single window is present at first @@ -1808,7 +1829,7 @@ TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { // Now, an obscuring window appears! sp obscuringWindow = sp::make(application, mDispatcher, "Obscuring window", - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, /*createInputChannel=*/false); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); @@ -1841,8 +1862,8 @@ TEST_F(InputDispatcherTest, HoverWhileWindowAppears) { */ TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); // Only a single window is present at first @@ -1857,7 +1878,7 @@ TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { // Now, an obscuring window appears! sp obscuringWindow = sp::make(application, mDispatcher, "Obscuring window", - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, /*createInputChannel=*/false); obscuringWindow->setFrame(Rect(0, 0, 200, 200)); obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); @@ -1899,8 +1920,8 @@ TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) { */ TEST_F(InputDispatcherTest, HoverMoveAndScroll) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -1931,8 +1952,8 @@ using InputDispatcherMultiDeviceTest = InputDispatcherTest; TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -1978,8 +1999,8 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) { TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2027,10 +2048,10 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) { TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 200, 200)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); @@ -2086,10 +2107,10 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) { TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 200, 200)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); @@ -2148,8 +2169,8 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) { TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2200,8 +2221,8 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) { TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2254,8 +2275,8 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) { TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2308,8 +2329,8 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) { TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2363,8 +2384,8 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) { TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2415,8 +2436,8 @@ TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) { TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2463,8 +2484,8 @@ TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) { TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2508,8 +2529,8 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) { TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -2562,12 +2583,12 @@ TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) { TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -2647,12 +2668,12 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) { TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -2725,12 +2746,12 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) { */ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) { std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -2784,18 +2805,18 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 400, 400)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); @@ -2860,18 +2881,18 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 400, 400)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); @@ -2939,18 +2960,18 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 400, 400)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -3015,18 +3036,18 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 400, 400)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -3092,8 +3113,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3172,8 +3193,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3247,8 +3268,8 @@ TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) { TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3284,8 +3305,8 @@ TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) { TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3328,12 +3349,12 @@ TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) { TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -3414,12 +3435,12 @@ TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) { TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 200, 200)); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 400, 200)); mDispatcher->onWindowInfosChanged( @@ -3481,8 +3502,8 @@ TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) { TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3548,8 +3569,8 @@ TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) { TEST_F(InputDispatcherMultiDeviceTest, StylusHoverWithTouchTap) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3608,12 +3629,13 @@ TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusDownBlocksTouch) { std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 100, 100)); sp sbtRightWindow = sp::make(application, mDispatcher, - "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT); + "Stylus blocks touch (right) window", + ui::LogicalDisplayId::DEFAULT); sbtRightWindow->setFrame(Rect(100, 100, 200, 200)); sbtRightWindow->setGlobalStylusBlocksTouch(true); @@ -3680,12 +3702,13 @@ TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusHoverBlocksTouch) { std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 100, 100)); sp sbtRightWindow = sp::make(application, mDispatcher, - "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT); + "Stylus blocks touch (right) window", + ui::LogicalDisplayId::DEFAULT); sbtRightWindow->setFrame(Rect(100, 100, 200, 200)); sbtRightWindow->setGlobalStylusBlocksTouch(true); @@ -3746,13 +3769,13 @@ TEST_F(InputDispatcherMultiDeviceTest, GlobalStylusHoverBlocksTouch) { */ TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 200, 200)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setNoInputChannel(true); window->setFrame(Rect(0, 0, 200, 200)); @@ -3805,8 +3828,8 @@ TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) { TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -3844,13 +3867,13 @@ TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) { TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 200, 200)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); @@ -3947,13 +3970,13 @@ TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) { TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 200, 200)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); @@ -4171,14 +4194,14 @@ TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTime) { TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { std::shared_ptr application = std::make_shared(); - sp windowLeft = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp windowLeft = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); windowLeft->setFrame(Rect(0, 0, 600, 800)); - sp windowRight = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp windowRight = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); windowRight->setFrame(Rect(600, 0, 1200, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged( {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0}); @@ -4238,7 +4261,7 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { .buttonState(0) .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT); + windowLeft->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); // Move mouse cursor back to right window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -4262,8 +4285,8 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) { TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 600, 800)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4322,8 +4345,8 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) { TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 600, 800)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4376,16 +4399,16 @@ TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) { TEST_F(InputDispatcherTest, HoverWithSpyWindows) { std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 600, 800)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 600, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); // Send mouse cursor to the window @@ -4409,16 +4432,16 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 600, 800)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 600, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); // Send mouse cursor to the window @@ -4516,16 +4539,16 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(0, 0, 600, 800)); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 600, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); // Send mouse cursor to the window @@ -4612,11 +4635,11 @@ TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) { // directly in this test. TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 1200, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4663,7 +4686,7 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { .buttonState(0) .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400)) .build())); - window->consumeMotionUp(ADISPLAY_ID_DEFAULT); + window->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail. @@ -4682,11 +4705,11 @@ TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) { */ TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 1200, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4710,11 +4733,11 @@ TEST_F_WITH_FLAGS(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash, REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags, a11y_crash_on_inconsistent_event_stream))) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 1200, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4736,8 +4759,8 @@ TEST_F_WITH_FLAGS(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash, TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4770,8 +4793,8 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) { TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4803,8 +4826,8 @@ TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) { TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4843,8 +4866,8 @@ TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) { TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) { SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4878,7 +4901,7 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { std::shared_ptr application = std::make_shared(); sp windowDefaultDisplay = sp::make(application, mDispatcher, "DefaultDisplay", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); windowDefaultDisplay->setFrame(Rect(0, 0, 600, 800)); sp windowSecondDisplay = sp::make(application, mDispatcher, "SecondDisplay", @@ -4894,7 +4917,7 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(600)) .build())); windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)); @@ -4913,7 +4936,7 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { injectMotionEvent(*mDispatcher, MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .pointer(PointerBuilder(0, ToolType::MOUSE).x(400).y(700)) .build())); windowDefaultDisplay->consumeMotionEvent( @@ -4925,14 +4948,14 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) { TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) { std::shared_ptr application = std::make_shared(); - sp windowLeft = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp windowLeft = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); windowLeft->setFrame(Rect(0, 0, 600, 800)); - sp windowRight = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp windowRight = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); windowRight->setFrame(Rect(600, 0, 1200, 800)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged( {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0}); @@ -4941,15 +4964,16 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) { // left window. This event should be dispatched to the left window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE, - ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400})); - windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT, {610, 400}, {599, 400})); + windowLeft->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); windowRight->assertNoEvents(); } TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -4957,41 +4981,44 @@ TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); // Window should receive key down event. - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); // When device reset happens, that key stream should be terminated with FLAG_CANCELED // on the app side. mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); - window->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED); + window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT, AKEY_EVENT_FLAG_CANCELED); } TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); // Window should receive motion down event. - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); // When device reset happens, that motion stream should be terminated with ACTION_CANCEL // on the app side. mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); window->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); } TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -5014,8 +5041,9 @@ TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) { TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -5023,13 +5051,14 @@ TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { window->consumeFocusEvent(true); - const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); + const NotifyKeyArgs keyArgs = + generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT); const std::chrono::milliseconds interceptKeyTimeout = 50ms; const nsecs_t injectTime = keyArgs.eventTime; mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout); mDispatcher->notifyKey(keyArgs); // The dispatching time should be always greater than or equal to intercept key timeout. - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >= std::chrono::nanoseconds(interceptKeyTimeout).count()); } @@ -5039,8 +5068,9 @@ TEST_F(InputDispatcherTest, InterceptKeyByPolicy) { */ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -5048,15 +5078,15 @@ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); // Set a value that's significantly larger than the default consumption timeout. If the // implementation is correct, the actual value doesn't matter; it won't slow down the test. mFakePolicy->setInterceptKeyTimeout(600ms); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT)); // Window should receive key event immediately when same key up. - window->consumeKeyUp(ADISPLAY_ID_DEFAULT); + window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); } /** @@ -5068,13 +5098,13 @@ TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) { */ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect{0, 0, 100, 100}); sp outsideWindow = sp::make(application, mDispatcher, "Outside Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); outsideWindow->setFrame(Rect{100, 100, 200, 200}); outsideWindow->setWatchOutsideTouch(true); // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped @@ -5082,8 +5112,8 @@ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) { // Tap on first window. mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{50, 50}})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {PointF{50, 50}})); window->consumeMotionDown(); // The coordinates of the tap in 'outsideWindow' are relative to its top left corner. // Therefore, we should offset them by (100, 100) relative to the screen's top left corner. @@ -5092,7 +5122,7 @@ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) { // Ensure outsideWindow doesn't get any more events for the gesture. mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{51, 51}})); + ui::LogicalDisplayId::DEFAULT, {PointF{51, 51}})); window->consumeMotionMove(); outsideWindow->assertNoEvents(); } @@ -5116,13 +5146,13 @@ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMu std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect{0, 0, 100, 100}); leftWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101}); sp outsideWindow = sp::make(application, mDispatcher, "Outside Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); outsideWindow->setFrame(Rect{100, 100, 200, 200}); outsideWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101}); outsideWindow->setWatchOutsideTouch(true); @@ -5131,7 +5161,7 @@ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMu std::make_shared(); sp rightWindow = sp::make(anotherApplication, mDispatcher, "Right Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect{100, 0, 200, 100}); rightWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202}); @@ -5195,32 +5225,33 @@ TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMu TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { // There are three windows that do not overlap. `window` wants to WATCH_OUTSIDE_TOUCH. std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "First Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "First Window", + ui::LogicalDisplayId::DEFAULT); window->setWatchOutsideTouch(true); window->setFrame(Rect{0, 0, 100, 100}); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setFrame(Rect{100, 100, 200, 200}); sp thirdWindow = sp::make(application, mDispatcher, "Third Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); thirdWindow->setFrame(Rect{200, 200, 300, 300}); mDispatcher->onWindowInfosChanged( {{*window->getInfo(), *secondWindow->getInfo(), *thirdWindow->getInfo()}, {}, 0, 0}); // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE. - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{-10, -10}})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {PointF{-10, -10}})); window->assertNoEvents(); secondWindow->assertNoEvents(); // The second pointer lands inside `secondWindow`, which should receive a DOWN event. // Now, `window` should get ACTION_OUTSIDE. mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {PointF{-10, -10}, PointF{105, 105}})); const std::map expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}}; window->consumeMotionEvent( @@ -5231,7 +5262,8 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { // The third pointer lands inside `thirdWindow`, which should receive a DOWN event. There is // no ACTION_OUTSIDE sent to `window` because one has already been sent for this gesture. mDispatcher->notifyMotion( - generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}})); window->assertNoEvents(); secondWindow->consumeMotionMove(); @@ -5240,8 +5272,9 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) { TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -5249,13 +5282,15 @@ TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { window->consumeFocusEvent(true); - const NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); - const NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); + const NotifyKeyArgs keyDown = + generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT); + const NotifyKeyArgs keyUp = + generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT); mDispatcher->notifyKey(keyDown); mDispatcher->notifyKey(keyUp); - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); - window->consumeKeyUp(ADISPLAY_ID_DEFAULT); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); + window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); // All windows are removed from the display. Ensure that we can no longer dispatch to it. mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); @@ -5269,22 +5304,23 @@ TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) { TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); // Ensure window is non-split and have some transform. window->setPreventSplitting(true); window->setWindowOffset(20, 40); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50)) @@ -5315,12 +5351,12 @@ TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) { std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left splittable Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWindow->setPreventSplitting(false); leftWindow->setFrame(Rect(0, 0, 100, 100)); sp rightWindow = sp::make(application, mDispatcher, "Right non-splittable Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWindow->setPreventSplitting(true); rightWindow->setFrame(Rect(100, 100, 200, 200)); mDispatcher->onWindowInfosChanged( @@ -5343,12 +5379,12 @@ TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) { TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); sp trustedOverlay = sp::make(application, mDispatcher, "Trusted Overlay", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); trustedOverlay->setSpy(true); trustedOverlay->setTrustedOverlay(true); @@ -5411,8 +5447,8 @@ TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) { TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -5468,8 +5504,8 @@ TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) { */ TEST_F(InputDispatcherTest, MultiplePointersWithRotatingWindow) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 400, 400)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -5534,13 +5570,14 @@ TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTarg std::shared_ptr application = std::make_shared(); sp spyWindowDefaultDisplay = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindowDefaultDisplay->setTrustedOverlay(true); spyWindowDefaultDisplay->setSpy(true); sp windowDefaultDisplay = sp::make(application, mDispatcher, "DefaultDisplay", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); windowDefaultDisplay->setWindowTransform(1, 0, 0, 1); sp windowSecondDisplay = windowDefaultDisplay->clone(SECOND_DISPLAY_ID); @@ -5554,10 +5591,10 @@ TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTarg 0, 0}); - // Send down to ADISPLAY_ID_DEFAULT + // Send down to ui::LogicalDisplayId::DEFAULT ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 100})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; spyWindowDefaultDisplay->consumeMotionDown(); @@ -5570,10 +5607,10 @@ TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTarg ASSERT_NE(nullptr, event); EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction()); - // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so - // the coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y - // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of - // SECOND_DISPLAY_ID, the x and y coordinates are 200 + // The cancel event is sent to windowDefaultDisplay of the ui::LogicalDisplayId::DEFAULT + // display, so the coordinates of the cancel are converted by windowDefaultDisplay's transform, + // the x and y coordinates are both 100, otherwise if the cancel event is sent to + // windowSecondDisplay of SECOND_DISPLAY_ID, the x and y coordinates are 200 EXPECT_EQ(100, event->getX(0)); EXPECT_EQ(100, event->getY(0)); } @@ -5617,7 +5654,7 @@ public: // respectively. ui::Transform displayTransform; displayTransform.set(2, 0, 0, 4); - addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform); + addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform); std::shared_ptr application = std::make_shared(); @@ -5625,13 +5662,13 @@ public: // Add two windows to the display. Their frames are represented in the display space. sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setFrame(Rect(0, 0, 100, 200), displayTransform); addWindow(firstWindow); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setFrame(Rect(100, 200, 200, 400), displayTransform); addWindow(secondWindow); return {std::move(firstWindow), std::move(secondWindow)}; @@ -5648,8 +5685,8 @@ TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) // selected so that if the hit test was performed with the point and the bounds being in // different coordinate spaces, the event would end up in the incorrect window. mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{75, 55}})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {PointF{75, 55}})); firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); @@ -5662,7 +5699,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, InjectionInLogicalDisplaySpace) { // Send down to the first window. The point is represented in the logical display space. The // point is selected so that if the hit test was done in logical display space, then it would // end up in the incorrect window. - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, PointF{75 * 2, 55 * 4}); firstWindow->consumeMotionDown(); @@ -5681,7 +5718,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, InjectionWithTransformInLogicalDisp const vec2 untransformedPoint = injectedEventTransform.inverse().transform(expectedPoint); MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER) .x(untransformedPoint.x) @@ -5700,9 +5737,9 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate auto [firstWindow, secondWindow] = setupScaledDisplayScenario(); // Send down to the second window. - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {PointF{150, 220}})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {PointF{150, 220}})); firstWindow->assertNoEvents(); std::unique_ptr event = secondWindow->consumeMotionEvent(); @@ -5724,17 +5761,17 @@ TEST_F(InputDispatcherDisplayProjectionTest, CancelMotionWithCorrectCoordinates) auto [firstWindow, secondWindow] = setupScaledDisplayScenario(); // The monitor will always receive events in the logical display's coordinate space, because // it does not have a window. - FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ADISPLAY_ID_DEFAULT}; + FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ui::LogicalDisplayId::DEFAULT}; // Send down to the first window. mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{50, 100}})); + ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}})); firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400))); monitor.consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400))); // Second pointer goes down on second window. mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}, PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80))); const std::map expectedMonitorPointers{{0, PointF{100, 400}}, @@ -5755,7 +5792,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeDownWithCorrectCoordinate // Send down to the first window. mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {PointF{50, 100}})); + ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}})); firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400))); // The pointer is transferred to the second window, and the second window receives it in the @@ -5770,13 +5807,15 @@ TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverEnterExitWithCorrect // Send hover move to the second window, and ensure it shows up as hover enter. mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}})); + ui::LogicalDisplayId::DEFAULT, + {PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithCoords(100, 80), WithRawCoords(300, 880))); // Touch down at the same location and ensure a hover exit is synthesized. mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}})); + ui::LogicalDisplayId::DEFAULT, + {PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80), WithRawCoords(300, 880))); secondWindow->consumeMotionEvent( @@ -5801,14 +5840,16 @@ TEST_F(InputDispatcherDisplayProjectionTest, // Send hover move to the second window, and ensure it shows up as hover enter. mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}})); + ui::LogicalDisplayId::DEFAULT, + {PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithCoords(100, 80), WithRawCoords(300, 880))); // Touch down at the same location and ensure a hover exit is synthesized for the correct // display. mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}})); + ui::LogicalDisplayId::DEFAULT, + {PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80), WithRawCoords(300, 880))); secondWindow->consumeMotionEvent( @@ -5822,7 +5863,8 @@ TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverCancelationWithCorre // Send hover enter to second window mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}})); + ui::LogicalDisplayId::DEFAULT, + {PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithCoords(100, 80), WithRawCoords(300, 880))); @@ -5850,17 +5892,18 @@ TEST_F(InputDispatcherDisplayProjectionTest, // Send hover enter to second window mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{150, 220}})); + ui::LogicalDisplayId::DEFAULT, + {PointF{150, 220}})); secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithCoords(100, 80), WithRawCoords(300, 880), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); mDispatcher->cancelCurrentTouch(); // Ensure the cancelation happens with the correct displayId and the correct coordinates. secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80), WithRawCoords(300, 880), - WithDisplayId(ADISPLAY_ID_DEFAULT))); + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); secondWindow->assertNoEvents(); firstWindow->assertNoEvents(); } @@ -5887,13 +5930,13 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight; const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation), logicalDisplayWidth, logicalDisplayHeight); - addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform); + addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform); // Create a window with its bounds determined in the logical display. const Rect frameInLogicalDisplay(100, 100, 200, 300); const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(frameInDisplay, displayTransform); addWindow(window); @@ -5903,14 +5946,14 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) for (const auto pointInsideWindow : insidePoints) { const vec2 p = displayTransform.inverse().transform(pointInsideWindow); const PointF pointInDisplaySpace{p.x, p.y}; - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); window->consumeMotionDown(); - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); window->consumeMotionUp(); } @@ -5920,13 +5963,13 @@ TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) for (const auto pointOutsideWindow : outsidePoints) { const vec2 p = displayTransform.inverse().transform(pointOutsideWindow); const PointF pointInDisplaySpace{p.x, p.y}; - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); } window->assertNoEvents(); } @@ -5950,7 +5993,7 @@ TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOri const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight; const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation), logicalDisplayWidth, logicalDisplayHeight); - addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform); + addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform); // Create a window that not trusted. const Rect untrustedWindowFrameInLogicalDisplay(100, 100, 200, 300); @@ -5960,7 +6003,7 @@ TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOri sp untrustedWindow = sp::make(untrustedWindowApplication, mDispatcher, "UntrustedWindow", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); untrustedWindow->setFrame(untrustedWindowFrameInDisplay, displayTransform); untrustedWindow->setTrustedOverlay(false); untrustedWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED); @@ -5976,7 +6019,7 @@ TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOri sp simpleAppWindow = sp::make(application, mDispatcher, "SimpleAppWindow", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); simpleAppWindow->setFrame(simpleAppWindowFrameInDisplay, displayTransform); simpleAppWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202}); addWindow(simpleAppWindow); @@ -5989,12 +6032,12 @@ TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOri for (const auto untrustedPoint : untrustedPoints) { const vec2 p = displayTransform.inverse().transform(untrustedPoint); const PointF pointInDisplaySpace{p.x, p.y}; - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); } untrustedWindow->assertNoEvents(); simpleAppWindow->assertNoEvents(); @@ -6005,15 +6048,15 @@ TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOri for (const auto trustedPoint : trustedPoints) { const vec2 p = displayTransform.inverse().transform(trustedPoint); const PointF pointInDisplaySpace{p.x, p.y}; - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); - simpleAppWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); + simpleAppWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInDisplaySpace})); - simpleAppWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace})); + simpleAppWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); } untrustedWindow->assertNoEvents(); @@ -6040,13 +6083,14 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // Create a couple of windows sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setDupTouchToWallpaper(true); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); sp wallpaper = - sp::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper", + ui::LogicalDisplayId::DEFAULT); wallpaper->setIsWallpaper(true); // Add the windows to the dispatcher, and ensure the first window is focused mDispatcher->onWindowInfosChanged( @@ -6056,12 +6100,13 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); - wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaper->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // Dispatcher reports pointer down outside focus for the wallpaper mFakePolicy->assertOnPointerDownEquals(wallpaper->getToken()); @@ -6071,17 +6116,19 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) { ASSERT_TRUE(success); // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); - wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + wallpaper->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); // There should not be any changes to the focused window when transferring touch ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertOnPointerDownWasNotCalled()); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper->assertNoEvents(); } @@ -6101,14 +6148,15 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { std::shared_ptr application = std::make_shared(); // Create a couple of windows + a spy window - sp spyWindow = - sp::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spyWindow = sp::make(application, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spyWindow->setTrustedOverlay(true); spyWindow->setSpy(true); - sp firstWindow = - sp::make(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT); + sp firstWindow = sp::make(application, mDispatcher, "First", + ui::LogicalDisplayId::DEFAULT); sp secondWindow = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); // Add the windows to the dispatcher mDispatcher->onWindowInfosChanged( @@ -6116,7 +6164,8 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); // Only the first window and spy should get the down event spyWindow->consumeMotionDown(); firstWindow->consumeMotionDown(); @@ -6128,15 +6177,17 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) { ASSERT_TRUE(success); // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets no events and the second+spy get up firstWindow->assertNoEvents(); spyWindow->consumeMotionUp(); - secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { @@ -6147,11 +6198,11 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { // Create a couple of windows sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setPreventSplitting(true); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setPreventSplitting(true); // Add the windows to the dispatcher @@ -6160,15 +6211,16 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchPoint})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {touchPoint})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send pointer down to the first window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); + ui::LogicalDisplayId::DEFAULT, + {touchPoint, touchPoint})); // Only the first window should get the pointer down event firstWindow->consumeMotionPointerDown(1); secondWindow->assertNoEvents(); @@ -6179,24 +6231,27 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { ASSERT_TRUE(success); // The first window gets cancel and the second gets down and pointer down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); - secondWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, + secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionPointerDown(1, ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send pointer up to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint})); + ui::LogicalDisplayId::DEFAULT, + {touchPoint, touchPoint})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); - secondWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, + secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { @@ -6205,19 +6260,21 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { // Create a couple of windows sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setDupTouchToWallpaper(true); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setDupTouchToWallpaper(true); sp wallpaper1 = - sp::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper1", + ui::LogicalDisplayId::DEFAULT); wallpaper1->setIsWallpaper(true); sp wallpaper2 = - sp::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Wallpaper2", + ui::LogicalDisplayId::DEFAULT); wallpaper2->setIsWallpaper(true); // Add the windows to the dispatcher mDispatcher->onWindowInfosChanged({{*firstWindow->getInfo(), *wallpaper1->getInfo(), @@ -6228,12 +6285,13 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); - wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaper1->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); wallpaper2->assertNoEvents(); // Transfer touch focus to the second window @@ -6243,19 +6301,21 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) { // The first window gets cancel and the second gets down firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); - wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, EXPECTED_WALLPAPER_FLAGS); - wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, + secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + wallpaper1->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS); + wallpaper2->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets no events and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); wallpaper1->assertNoEvents(); - wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, + wallpaper2->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } @@ -6268,7 +6328,7 @@ INSTANTIATE_TEST_SUITE_P( [&](const std::unique_ptr& dispatcher, sp /*ignored*/, sp destChannelToken) { return dispatcher->transferTouchOnDisplay(destChannelToken, - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); }, [&](const std::unique_ptr& dispatcher, sp from, sp to) { @@ -6281,12 +6341,12 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setFrame(Rect(0, 0, 600, 400)); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setFrame(Rect(0, 400, 600, 800)); // Add the windows to the dispatcher @@ -6298,15 +6358,15 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); @@ -6316,24 +6376,25 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { mDispatcher->transferTouchGesture(firstWindow->getToken(), secondWindow->getToken()); // The first window gets cancel and the new gets pointer down (it already saw down) firstWindow->consumeMotionCancel(); - secondWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, + secondWindow->consumeMotionPointerDown(1, ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send pointer up to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); - secondWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, + secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets nothing and the second gets up firstWindow->assertNoEvents(); - secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } // Same as TransferTouch_TwoPointersSplitTouch, but using 'transferTouchOnDisplay' api. @@ -6345,12 +6406,12 @@ TEST_F(InputDispatcherTest, TransferTouchOnDisplay_TwoPointersSplitTouch) { sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setFrame(Rect(0, 0, 600, 400)); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setFrame(Rect(0, 400, 600, 800)); // Add the windows to the dispatcher @@ -6362,23 +6423,23 @@ TEST_F(InputDispatcherTest, TransferTouchOnDisplay_TwoPointersSplitTouch) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); secondWindow->consumeMotionDown(); // Transfer touch focus to the second window - const bool transferred = - mDispatcher->transferTouchOnDisplay(secondWindow->getToken(), ADISPLAY_ID_DEFAULT); + const bool transferred = mDispatcher->transferTouchOnDisplay(secondWindow->getToken(), + ui::LogicalDisplayId::DEFAULT); // The 'transferTouchOnDisplay' call should not succeed, because there are 2 touched windows ASSERT_FALSE(transferred); firstWindow->assertNoEvents(); @@ -6387,7 +6448,7 @@ TEST_F(InputDispatcherTest, TransferTouchOnDisplay_TwoPointersSplitTouch) { // The rest of the dispatch should proceed as normal // Send pointer up to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond})); // The first window gets MOVE and the second gets pointer up firstWindow->consumeMotionMove(); @@ -6395,7 +6456,7 @@ TEST_F(InputDispatcherTest, TransferTouchOnDisplay_TwoPointersSplitTouch) { // Send up event to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets nothing and the second gets up firstWindow->consumeMotionUp(); secondWindow->assertNoEvents(); @@ -6407,13 +6468,16 @@ TEST_F(InputDispatcherTest, TransferTouchOnDisplay_TwoPointersSplitTouch) { TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) { std::shared_ptr application = std::make_shared(); sp firstWindowInPrimary = - sp::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W1", + ui::LogicalDisplayId::DEFAULT); firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100)); sp secondWindowInPrimary = - sp::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W2", + ui::LogicalDisplayId::DEFAULT); secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100)); - sp mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT); + sp mirrorWindowInPrimary = + firstWindowInPrimary->clone(ui::LogicalDisplayId::DEFAULT); mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200)); sp firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID); @@ -6432,35 +6496,36 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) { 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Window should receive motion event. - firstWindowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT); + firstWindowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); // Transfer touch ASSERT_TRUE(mDispatcher->transferTouchGesture(firstWindowInPrimary->getToken(), secondWindowInPrimary->getToken())); // The first window gets cancel. firstWindowInPrimary->consumeMotionCancel(); - secondWindowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT, + secondWindowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {150, 50})) + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; firstWindowInPrimary->assertNoEvents(); - secondWindowInPrimary->consumeMotionMove(ADISPLAY_ID_DEFAULT, + secondWindowInPrimary->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; firstWindowInPrimary->assertNoEvents(); - secondWindowInPrimary->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindowInPrimary->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } // Same as TransferTouch_CloneSurface, but this touch on the secondary display and use @@ -6468,13 +6533,16 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) { TEST_F(InputDispatcherTest, TransferTouchOnDisplay_CloneSurface) { std::shared_ptr application = std::make_shared(); sp firstWindowInPrimary = - sp::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W1", + ui::LogicalDisplayId::DEFAULT); firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100)); sp secondWindowInPrimary = - sp::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "D_1_W2", + ui::LogicalDisplayId::DEFAULT); secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100)); - sp mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT); + sp mirrorWindowInPrimary = + firstWindowInPrimary->clone(ui::LogicalDisplayId::DEFAULT); mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200)); sp firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID); @@ -6527,8 +6595,9 @@ TEST_F(InputDispatcherTest, TransferTouchOnDisplay_CloneSurface) { TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -6536,10 +6605,10 @@ TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); // Window should receive key down event. - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); // Should have poked user activity mDispatcher->waitForIdle(); @@ -6548,8 +6617,9 @@ TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) { TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setDisableUserActivity(true); window->setFocusable(true); @@ -6558,10 +6628,10 @@ TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); // Window should receive key down event. - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); // Should have poked user activity mDispatcher->waitForIdle(); @@ -6570,8 +6640,9 @@ TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) { TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -6579,7 +6650,8 @@ TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey( + generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); // System key is not passed down @@ -6591,8 +6663,9 @@ TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -6600,7 +6673,8 @@ TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey( + generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); // System key is not passed down @@ -6612,8 +6686,9 @@ TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); window->setDisableUserActivity(true); window->setFocusable(true); @@ -6622,7 +6697,8 @@ TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey( + generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); // System key is not passed down @@ -6634,18 +6710,19 @@ TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) { TEST_F(InputDispatcherTest, InjectedTouchesPokeUserActivity) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {100, 100})) + ui::LogicalDisplayId::DEFAULT, {100, 100})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); // Should have poked user activity mDispatcher->waitForIdle(); @@ -6654,12 +6731,13 @@ TEST_F(InputDispatcherTest, InjectedTouchesPokeUserActivity) { TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); window->assertNoEvents(); @@ -6668,19 +6746,21 @@ TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) // If a window is touchable, but does not have focus, it should receive motion events, but not keys TEST_F(InputDispatcherTest, UnfocusedWindow_ReceivesMotionsButNotKeys) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); // Send key - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); // Send motion mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); // Window should receive only the motion event - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); window->assertNoEvents(); // Key event or focus event will not be received } @@ -6689,12 +6769,12 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { sp firstWindow = sp::make(application, mDispatcher, "First Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); firstWindow->setFrame(Rect(0, 0, 600, 400)); sp secondWindow = sp::make(application, mDispatcher, "Second Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); secondWindow->setFrame(Rect(0, 400, 600, 800)); // Add the windows to the dispatcher @@ -6706,15 +6786,15 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { // Send down to the first window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInFirst})); // Only the first window should get the down event firstWindow->consumeMotionDown(); secondWindow->assertNoEvents(); // Send down to the second window mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, + ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond})); // The first window gets a move and the second a down firstWindow->consumeMotionMove(); @@ -6722,17 +6802,17 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { // Send pointer cancel to the second window NotifyMotionArgs pointerUpMotionArgs = - generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {pointInFirst, pointInSecond}); + generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond}); pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED; mDispatcher->notifyMotion(pointerUpMotionArgs); // The first window gets move and the second gets cancel. - firstWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED); - secondWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED); + firstWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_CANCELED); + secondWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_CANCELED); // Send up event. mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); // The first window gets up and the second gets nothing. firstWindow->consumeMotionUp(); secondWindow->assertNoEvents(); @@ -6741,8 +6821,8 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) { TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); std::array graphicsTimeline; graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2; @@ -6765,28 +6845,29 @@ using InputDispatcherMonitorTest = InputDispatcherTest; */ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; // Both the foreground window and the global monitor should receive the touch down window->consumeMotionDown(); - monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 200})) + ui::LogicalDisplayId::DEFAULT, {110, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionMove(); - monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT); // Now the foreground window goes away mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); @@ -6797,41 +6878,47 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDis // cause a cancel for the monitor, as well. ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {120, 200})) + ui::LogicalDisplayId::DEFAULT, {120, 200})) << "Injection should fail because the window was removed"; window->assertNoEvents(); // Global monitor now gets the cancel - monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT); } TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); - monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) { - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); // Pilfer pointers from the monitor. // This should not do anything and the window should continue to receive events. @@ -6839,27 +6926,30 @@ TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)) + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT); - window->consumeMotionMove(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT); + window->consumeMotionMove(ui::LogicalDisplayId::DEFAULT); } TEST_F(InputDispatcherMonitorTest, NoWindowTransform) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); window->setWindowOffset(20, 40); window->setWindowTransform(0, 1, -1, 0); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); std::unique_ptr event = monitor.consumeMotion(); ASSERT_NE(nullptr, event); // Even though window has transform, gesture monitor must not. @@ -6868,10 +6958,12 @@ TEST_F(InputDispatcherMonitorTest, NoWindowTransform) { TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) { std::shared_ptr application = std::make_shared(); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::FAILED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Injection should fail if there is a monitor, but no touchable window"; monitor.assertNoEvents(); } @@ -6889,20 +6981,21 @@ TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) { */ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisplayReceiveEvents) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "The down event injected into the first display should succeed"; window->consumeMotionDown(); - monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, @@ -6913,19 +7006,19 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisp // Continue to inject event to first display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 220})) + ui::LogicalDisplayId::DEFAULT, {110, 220})) << "The move event injected into the first display should succeed"; window->consumeMotionMove(); - monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {110, 220})) << "The up event injected into the first display should succeed"; window->consumeMotionUp(); - monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionUp(ui::LogicalDisplayId::DEFAULT); window->assertNoEvents(); monitor.assertNoEvents(); @@ -6949,24 +7042,25 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisp */ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMonitorTouchCanceled) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); sp secondWindow = sp::make(application, mDispatcher, "SecondForeground", SECOND_DISPLAY_ID); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); // There is a foreground window on both displays. mDispatcher->onWindowInfosChanged({{*window->getInfo(), *secondWindow->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "The down event injected into the first display should succeed"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); - monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, @@ -6994,11 +7088,11 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMoni ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 200})) + ui::LogicalDisplayId::DEFAULT, {110, 200})) << "The move event injected into the first display should succeed"; window->consumeMotionMove(); - monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, @@ -7007,12 +7101,12 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMoni "touchable window"; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {110, 220})) << "The up event injected into the first display should succeed"; - window->consumeMotionUp(ADISPLAY_ID_DEFAULT); - monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT); + window->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); + monitor.consumeMotionUp(ui::LogicalDisplayId::DEFAULT); window->assertNoEvents(); monitor.assertNoEvents(); @@ -7030,22 +7124,23 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMoni */ TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Foreground", + ui::LogicalDisplayId::DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); ui::Transform transform; transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1}); gui::DisplayInfo displayInfo; - displayInfo.displayId = ADISPLAY_ID_DEFAULT; + displayInfo.displayId = ui::LogicalDisplayId::DEFAULT; displayInfo.transform = transform; mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "The down event injected should succeed"; window->consumeMotionDown(); @@ -7055,7 +7150,7 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 220})) + ui::LogicalDisplayId::DEFAULT, {110, 220})) << "The move event injected should succeed"; window->consumeMotionMove(); @@ -7071,19 +7166,19 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 220})) + ui::LogicalDisplayId::DEFAULT, {110, 220})) << "The move event injected should failed"; // Now foreground should not receive any events, but monitor should receive a cancel event // with transform that same as display's display. std::unique_ptr cancelMotionEvent = monitor.consumeMotion(); EXPECT_EQ(transform, cancelMotionEvent->getTransform()); - EXPECT_EQ(ADISPLAY_ID_DEFAULT, cancelMotionEvent->getDisplayId()); + EXPECT_EQ(ui::LogicalDisplayId::DEFAULT, cancelMotionEvent->getDisplayId()); EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, cancelMotionEvent->getAction()); // Other event inject to this display should fail. ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 220})) + ui::LogicalDisplayId::DEFAULT, {110, 220})) << "The up event injected should fail because the touched window was removed"; window->assertNoEvents(); monitor.assertNoEvents(); @@ -7091,18 +7186,19 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) TEST_F(InputDispatcherTest, TestMoveEvent) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Fake Window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mDispatcher->notifyMotion(motionArgs); // Window should receive motion down event. - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); motionArgs.action = AMOTION_EVENT_ACTION_MOVE; motionArgs.id += 1; @@ -7111,7 +7207,7 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { motionArgs.pointerCoords[0].getX() - 10); mDispatcher->notifyMotion(motionArgs); - window->consumeMotionMove(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0); + window->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, /*expectedFlags=*/0); } /** @@ -7121,12 +7217,13 @@ TEST_F(InputDispatcherTest, TestMoveEvent) { */ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Test window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Test window", + ui::LogicalDisplayId::DEFAULT); const WindowInfo& windowInfo = *window->getInfo(); // Set focused application. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); SCOPED_TRACE("Check default value of touch mode"); @@ -7141,7 +7238,7 @@ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { SCOPED_TRACE("Disable touch mode"); mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid, - /*hasPermission=*/true, ADISPLAY_ID_DEFAULT); + /*hasPermission=*/true, ui::LogicalDisplayId::DEFAULT); window->consumeTouchModeEvent(false); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -7155,7 +7252,7 @@ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { SCOPED_TRACE("Enable touch mode again"); mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid, - /*hasPermission=*/true, ADISPLAY_ID_DEFAULT); + /*hasPermission=*/true, ui::LogicalDisplayId::DEFAULT); window->consumeTouchModeEvent(true); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -7167,10 +7264,11 @@ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) { TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Test window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Test window", + ui::LogicalDisplayId::DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -7205,23 +7303,24 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) { TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Test window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Test window", + ui::LogicalDisplayId::DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); ui::Transform transform; transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1}); gui::DisplayInfo displayInfo; - displayInfo.displayId = ADISPLAY_ID_DEFAULT; + displayInfo.displayId = ui::LogicalDisplayId::DEFAULT; displayInfo.transform = transform; mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0}); const NotifyMotionArgs motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mDispatcher->notifyMotion(motionArgs); std::unique_ptr event = window->consumeMotionEvent(); @@ -7308,11 +7407,12 @@ TEST_F(InputDispatcherTest, GeneratedHmac_ChangesWhenFieldsChange) { TEST_F(InputDispatcherTest, SetFocusedWindow) { std::shared_ptr application = std::make_shared(); - sp windowTop = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp windowTop = sp::make(application, mDispatcher, "Top", + ui::LogicalDisplayId::DEFAULT); sp windowSecond = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); // Top window is also focusable but is not granted focus. windowTop->setFocusable(true); @@ -7326,15 +7426,15 @@ TEST_F(InputDispatcherTest, SetFocusedWindow) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Focused window should receive event. - windowSecond->consumeKeyDown(ui::ADISPLAY_ID_NONE); + windowSecond->consumeKeyDown(ui::LogicalDisplayId::INVALID); windowTop->assertNoEvents(); } TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + sp window = sp::make(application, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); // Release channel for window is no longer valid. @@ -7351,10 +7451,10 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) { TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); window->setFocusable(false); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); @@ -7368,11 +7468,12 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) { TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { std::shared_ptr application = std::make_shared(); - sp windowTop = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp windowTop = sp::make(application, mDispatcher, "Top", + ui::LogicalDisplayId::DEFAULT); sp windowSecond = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); windowTop->setFocusable(true); windowSecond->setFocusable(true); @@ -7391,16 +7492,17 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Focused window should receive event. - windowSecond->consumeKeyDown(ui::ADISPLAY_ID_NONE); + windowSecond->consumeKeyDown(ui::LogicalDisplayId::INVALID); } TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) { std::shared_ptr application = std::make_shared(); - sp windowTop = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp windowTop = sp::make(application, mDispatcher, "Top", + ui::LogicalDisplayId::DEFAULT); sp windowSecond = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); windowTop->setFocusable(true); windowSecond->setFocusable(false); @@ -7414,18 +7516,18 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; // Event should be dropped. - windowTop->consumeKeyDown(ui::ADISPLAY_ID_NONE); + windowTop->consumeKeyDown(ui::LogicalDisplayId::INVALID); windowSecond->assertNoEvents(); } TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); sp previousFocusedWindow = sp::make(application, mDispatcher, "previousFocusedWindow", - ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); previousFocusedWindow->setFocusable(true); @@ -7442,7 +7544,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { // Injected key goes to pending queue. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE)); // Window does not get focus event or key down. window->assertNoEvents(); @@ -7454,14 +7556,14 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) { // Window receives focus event. window->consumeFocusEvent(true); // Focused window receives key down. - window->consumeKeyDown(ADISPLAY_ID_DEFAULT); + window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); } TEST_F(InputDispatcherTest, DisplayRemoved) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + sp window = sp::make(application, mDispatcher, "window", + ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); // window is granted focus. window->setFocusable(true); @@ -7470,7 +7572,7 @@ TEST_F(InputDispatcherTest, DisplayRemoved) { window->consumeFocusEvent(true); // When a display is removed window loses focus. - mDispatcher->displayRemoved(ADISPLAY_ID_DEFAULT); + mDispatcher->displayRemoved(ui::LogicalDisplayId::DEFAULT); window->consumeFocusEvent(false); } @@ -7502,10 +7604,11 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { constexpr gui::Uid SLIPPERY_UID{WINDOW_UID.val() + 1}; std::shared_ptr application = std::make_shared(); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); sp slipperyExitWindow = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Top", + ui::LogicalDisplayId::DEFAULT); slipperyExitWindow->setSlippery(true); // Make sure this one overlaps the bottom window slipperyExitWindow->setFrame(Rect(25, 25, 75, 75)); @@ -7514,7 +7617,8 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID); sp slipperyEnterWindow = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); slipperyExitWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged( @@ -7522,20 +7626,20 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) { // Use notifyMotion instead of injecting to avoid dealing with injection permissions mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {{50, 50}})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {{50, 50}})); slipperyExitWindow->consumeMotionDown(); slipperyExitWindow->setFrame(Rect(70, 70, 100, 100)); mDispatcher->onWindowInfosChanged( {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0}); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {{51, 51}})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {{51, 51}})); slipperyExitWindow->consumeMotionCancel(); - slipperyEnterWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, + slipperyEnterWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); } @@ -7550,12 +7654,14 @@ TEST_F(InputDispatcherTest, TouchSlippingIntoWindowThatDropsTouches) { std::shared_ptr application = std::make_shared(); sp leftSlipperyWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftSlipperyWindow->setSlippery(true); leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100)); sp rightDropTouchesWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100)); rightDropTouchesWindow->setDropInput(true); @@ -7586,12 +7692,14 @@ TEST_F(InputDispatcherTest, TouchSlippingIntoWindowThatDropsTouches) { TEST_F(InputDispatcherTest, InjectedTouchSlips) { std::shared_ptr application = std::make_shared(); sp originalWindow = - sp::make(application, mDispatcher, "Original", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Original", + ui::LogicalDisplayId::DEFAULT); originalWindow->setFrame(Rect(0, 0, 200, 200)); originalWindow->setSlippery(true); sp appearingWindow = - sp::make(application, mDispatcher, "Appearing", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Appearing", + ui::LogicalDisplayId::DEFAULT); appearingWindow->setFrame(Rect(0, 0, 200, 200)); mDispatcher->onWindowInfosChanged({{*originalWindow->getInfo()}, {}, 0, 0}); @@ -7645,17 +7753,18 @@ TEST_F(InputDispatcherTest, MultiDeviceSpyWindowSlipTest) { std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 100, 100)); leftWindow->setSlippery(true); sp rightWindow = sp::make(application, mDispatcher, "Right window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(100, 0, 200, 100)); sp spyWindow = - sp::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Spy Window", + ui::LogicalDisplayId::DEFAULT); spyWindow->setFrame(Rect(200, 0, 300, 100)); spyWindow->setSpy(true); spyWindow->setTrustedOverlay(true); @@ -7708,18 +7817,18 @@ TEST_F(InputDispatcherTest, MultiDeviceSlipperyWindowTest) { std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 100, 100)); leftWindow->setSlippery(true); sp middleWindow = sp::make(application, mDispatcher, "middle window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); middleWindow->setFrame(Rect(100, 0, 200, 100)); sp rightWindow = sp::make(application, mDispatcher, "Right window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(200, 0, 300, 100)); rightWindow->setSlippery(true); @@ -7778,20 +7887,21 @@ TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) { using Uid = gui::Uid; std::shared_ptr application = std::make_shared(); - sp leftWindow = - sp::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT); + sp leftWindow = sp::make(application, mDispatcher, "Left", + ui::LogicalDisplayId::DEFAULT); leftWindow->setFrame(Rect(0, 0, 100, 100)); leftWindow->setOwnerInfo(gui::Pid{1}, Uid{101}); sp rightSpy = - sp::make(application, mDispatcher, "Right spy", ADISPLAY_ID_DEFAULT); + sp::make(application, mDispatcher, "Right spy", + ui::LogicalDisplayId::DEFAULT); rightSpy->setFrame(Rect(100, 0, 200, 100)); rightSpy->setOwnerInfo(gui::Pid{2}, Uid{102}); rightSpy->setSpy(true); rightSpy->setTrustedOverlay(true); - sp rightWindow = - sp::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT); + sp rightWindow = sp::make(application, mDispatcher, "Right", + ui::LogicalDisplayId::DEFAULT); rightWindow->setFrame(Rect(100, 0, 200, 100)); rightWindow->setOwnerInfo(gui::Pid{3}, Uid{103}); @@ -7856,8 +7966,8 @@ TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) { TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); window->setOwnerInfo(gui::Pid{1}, gui::Uid{101}); @@ -7866,14 +7976,14 @@ TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) { ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true)); mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build()); - ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ADISPLAY_ID_DEFAULT)); + ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); ASSERT_NO_FATAL_FAILURE( mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {gui::Uid{101}})); // The UP actions are not treated as device interaction. mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build()); - ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ADISPLAY_ID_DEFAULT)); + ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled()); } @@ -7882,13 +7992,14 @@ TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) { std::shared_ptr application = std::make_shared(); sp left = sp::make(application, mDispatcher, "Left Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); left->setFrame(Rect(0, 0, 100, 100)); - sp right = sp::make(application, mDispatcher, - "Right Window", ADISPLAY_ID_DEFAULT); + sp right = + sp::make(application, mDispatcher, "Right Window", + ui::LogicalDisplayId::DEFAULT); right->setFrame(Rect(100, 0, 200, 100)); - sp spy = - sp::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT); + sp spy = sp::make(application, mDispatcher, "Spy Window", + ui::LogicalDisplayId::DEFAULT); spy->setFrame(Rect(0, 0, 200, 100)); spy->setTrustedOverlay(true); spy->setSpy(true); @@ -7897,8 +8008,9 @@ TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) { {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0}); // Send hover move to the left window, and ensure hover enter is synthesized with a new eventId. - NotifyMotionArgs notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{50, 50}}); + NotifyMotionArgs notifyArgs = + generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, + ui::LogicalDisplayId::DEFAULT, {PointF{50, 50}}); mDispatcher->notifyMotion(notifyArgs); std::unique_ptr leftEnter = left->consumeMotionEvent( @@ -7911,8 +8023,8 @@ TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) { WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER))); // Send move to the right window, and ensure hover exit and enter are synthesized with new ids. - notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, - {PointF{150, 50}}); + notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, + ui::LogicalDisplayId::DEFAULT, {PointF{150, 50}}); mDispatcher->notifyMotion(notifyArgs); std::unique_ptr leftExit = left->consumeMotionEvent( @@ -7934,8 +8046,8 @@ TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) { */ TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWithoutWindowTarget) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -7960,8 +8072,8 @@ TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWith */ TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) { std::shared_ptr application = std::make_shared(); - sp window = - sp::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + sp window = sp::make(application, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); window->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); @@ -7991,7 +8103,8 @@ protected: mApp = std::make_shared(); - mWindow = sp::make(mApp, mDispatcher, "Window", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "Window", + ui::LogicalDisplayId::DEFAULT); mWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); @@ -8303,7 +8416,8 @@ protected: void setUpWindow() { mApp = std::make_shared(); - mWindow = sp::make(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "Fake Window", + ui::LogicalDisplayId::DEFAULT); mWindow->setFocusable(true); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); @@ -8312,13 +8426,14 @@ protected: } void sendAndConsumeKeyDown(int32_t deviceId) { - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT); + NotifyKeyArgs keyArgs = + generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT); keyArgs.deviceId = deviceId; keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Otherwise it won't generate repeat event mDispatcher->notifyKey(keyArgs); // Window should receive key down event. - mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); } void expectKeyRepeatOnce(int32_t repeatCount) { @@ -8328,19 +8443,21 @@ protected: } void sendAndConsumeKeyUp(int32_t deviceId) { - NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT); + NotifyKeyArgs keyArgs = + generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT); keyArgs.deviceId = deviceId; keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Unless it won't generate repeat event mDispatcher->notifyKey(keyArgs); // Window should receive key down event. - mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, + mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT, /*expectedFlags=*/0); } void injectKeyRepeat(int32_t repeatCount) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, repeatCount, ADISPLAY_ID_DEFAULT)) + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, repeatCount, + ui::LogicalDisplayId::DEFAULT)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; } }; @@ -8401,7 +8518,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_StopsKeyRepeatAfterDisableInp sendAndConsumeKeyDown(DEVICE_ID); expectKeyRepeatOnce(/*repeatCount=*/1); mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID}); - mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT, + mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT, AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS); mWindow->assertNoEvents(); } @@ -8433,7 +8550,7 @@ TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_RepeatKeyEventsUseUniqueEvent TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_CorrectRepeatCountWhenInjectKeyRepeat) { injectKeyRepeat(0); - mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); for (int32_t repeatCount = 1; repeatCount <= 2; ++repeatCount) { expectKeyRepeatOnce(repeatCount); } @@ -8449,11 +8566,11 @@ public: InputDispatcherTest::SetUp(); application1 = std::make_shared(); - windowInPrimary = - sp::make(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT); + windowInPrimary = sp::make(application1, mDispatcher, "D_1", + ui::LogicalDisplayId::DEFAULT); // Set focus window for primary display, but focused display would be second one. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application1); windowInPrimary->setFocusable(true); mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0}); @@ -8494,9 +8611,10 @@ protected: TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch) { // Test touch down on primary display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); windowInSecondary->assertNoEvents(); // Test touch down on second display. @@ -8510,22 +8628,22 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch) TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) { // Test inject a key down with display id specified. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT)) + injectKeyDownNoRepeat(*mDispatcher, ui::LogicalDisplayId::DEFAULT)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - windowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT); + windowInPrimary->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); windowInSecondary->assertNoEvents(); // Test inject a key down without display id specified. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); - windowInSecondary->consumeKeyDown(ui::ADISPLAY_ID_NONE); + windowInSecondary->consumeKeyDown(ui::LogicalDisplayId::INVALID); // Remove all windows in secondary display. mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0}); // Old focus should receive a cancel event. - windowInSecondary->consumeKeyUp(ui::ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED); + windowInSecondary->consumeKeyUp(ui::LogicalDisplayId::INVALID, AKEY_EVENT_FLAG_CANCELED); // Test inject a key down, should timeout because of no target window. ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher)); @@ -8537,16 +8655,17 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) // Test per-display input monitors for motion event. TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { FakeMonitorReceiver monitorInPrimary = - FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); FakeMonitorReceiver monitorInSecondary = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); // Test touch down on primary display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT); - monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + monitorInPrimary.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); windowInSecondary->assertNoEvents(); monitorInSecondary.assertNoEvents(); @@ -8570,19 +8689,20 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) { // If specific a display, it will dispatch to the focused window of particular display, // or it will dispatch to the focused window of focused display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ui::ADISPLAY_ID_NONE)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, + ui::LogicalDisplayId::INVALID)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); monitorInPrimary.assertNoEvents(); - windowInSecondary->consumeMotionDown(ui::ADISPLAY_ID_NONE); - monitorInSecondary.consumeMotionDown(ui::ADISPLAY_ID_NONE); + windowInSecondary->consumeMotionDown(ui::LogicalDisplayId::INVALID); + monitorInSecondary.consumeMotionDown(ui::LogicalDisplayId::INVALID); } // Test per-display input monitors for key event. TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) { // Input monitor per display. FakeMonitorReceiver monitorInPrimary = - FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); FakeMonitorReceiver monitorInSecondary = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); @@ -8591,13 +8711,14 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) { << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); monitorInPrimary.assertNoEvents(); - windowInSecondary->consumeKeyDown(ui::ADISPLAY_ID_NONE); - monitorInSecondary.consumeKeyDown(ui::ADISPLAY_ID_NONE); + windowInSecondary->consumeKeyDown(ui::LogicalDisplayId::INVALID); + monitorInSecondary.consumeKeyDown(ui::LogicalDisplayId::INVALID); } TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) { sp secondWindowInPrimary = - sp::make(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT); + sp::make(application1, mDispatcher, "D_1_W2", + ui::LogicalDisplayId::DEFAULT); secondWindowInPrimary->setFocusable(true); mDispatcher->onWindowInfosChanged( {{*windowInPrimary->getInfo(), *secondWindowInPrimary->getInfo(), @@ -8611,25 +8732,26 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) // Test inject a key down. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT)) + injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; windowInPrimary->assertNoEvents(); windowInSecondary->assertNoEvents(); - secondWindowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT); + secondWindowInPrimary->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); } TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) { FakeMonitorReceiver monitorInPrimary = - FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); FakeMonitorReceiver monitorInSecondary = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID); // Test touch down on primary display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT); - monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT); + windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + monitorInPrimary.consumeMotionDown(ui::LogicalDisplayId::DEFAULT); // Test touch down on second display. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -8640,15 +8762,15 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) { // Trigger cancel touch. mDispatcher->cancelCurrentTouch(); - windowInPrimary->consumeMotionCancel(ADISPLAY_ID_DEFAULT); - monitorInPrimary.consumeMotionCancel(ADISPLAY_ID_DEFAULT); + windowInPrimary->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT); + monitorInPrimary.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT); windowInSecondary->consumeMotionCancel(SECOND_DISPLAY_ID); monitorInSecondary.consumeMotionCancel(SECOND_DISPLAY_ID); // Test inject a move motion event, no window/monitor should receive the event. ASSERT_EQ(InputEventInjectionResult::FAILED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {110, 200})) + ui::LogicalDisplayId::DEFAULT, {110, 200})) << "Inject motion event should return InputEventInjectionResult::FAILED"; windowInPrimary->assertNoEvents(); monitorInPrimary.assertNoEvents(); @@ -8671,11 +8793,11 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropKeyEvent_OnlyCancelCorresp // Send a key down on primary display mDispatcher->notifyKey( KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) .build()); - windowInPrimary->consumeKeyEvent( - AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT))); + windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), + WithDisplayId(ui::LogicalDisplayId::DEFAULT))); windowInSecondary->assertNoEvents(); // Send a key down on second display @@ -8691,7 +8813,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropKeyEvent_OnlyCancelCorresp // Send a valid key up event on primary display that will be dropped because it is stale NotifyKeyArgs staleKeyUp = KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT) .build(); static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms; @@ -8703,7 +8825,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropKeyEvent_OnlyCancelCorresp // Therefore, windowInPrimary should get the cancel event and windowInSecondary should not // receive any events. windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), - WithDisplayId(ADISPLAY_ID_DEFAULT), + WithDisplayId(ui::LogicalDisplayId::DEFAULT), WithFlags(AKEY_EVENT_FLAG_CANCELED))); windowInSecondary->assertNoEvents(); } @@ -8716,10 +8838,10 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropMotionEvent_OnlyCancelCorr mDispatcher->notifyMotion( MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .build()); windowInPrimary->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); windowInSecondary->assertNoEvents(); // Send touch down on second display. @@ -8735,7 +8857,7 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, WhenDropMotionEvent_OnlyCancelCorr // inject a valid MotionEvent on primary display that will be stale when it arrives. NotifyMotionArgs staleMotionUp = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) .build(); static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms; @@ -8790,19 +8912,19 @@ protected: // Test InputFilter for MotionEvent TEST_F(InputFilterTest, MotionEvent_InputFilter) { // Since the InputFilter is disabled by default, check if touch events aren't filtered. - testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false); + testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/false); testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false); // Enable InputFilter mDispatcher->setInputFilterEnabled(true); // Test touch on both primary and second display, and check if both events are filtered. - testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true); + testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/true); testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true); // Disable InputFilter mDispatcher->setInputFilterEnabled(false); // Test touch on both primary and second display, and check if both events aren't filtered. - testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false); + testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/false); testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false); } @@ -8831,7 +8953,7 @@ TEST_F(InputFilterTest, MotionEvent_UsesLogicalDisplayCoordinates_notifyMotion) secondDisplayTransform.set({-6.6, -5.5, -4.4, -3.3, -2.2, -1.1, 0, 0, 1}); std::vector displayInfos(2); - displayInfos[0].displayId = ADISPLAY_ID_DEFAULT; + displayInfos[0].displayId = ui::LogicalDisplayId::DEFAULT; displayInfos[0].transform = firstDisplayTransform; displayInfos[1].displayId = SECOND_DISPLAY_ID; displayInfos[1].transform = secondDisplayTransform; @@ -8842,7 +8964,8 @@ TEST_F(InputFilterTest, MotionEvent_UsesLogicalDisplayCoordinates_notifyMotion) mDispatcher->setInputFilterEnabled(true); // Ensure the correct transforms are used for the displays. - testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true, firstDisplayTransform); + testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/true, + firstDisplayTransform); testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true, secondDisplayTransform); } @@ -8861,9 +8984,9 @@ protected: std::shared_ptr application = std::make_shared(); mWindow = sp::make(application, mDispatcher, "Test Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mWindow->setFocusable(true); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); setFocusedWindow(mWindow); @@ -8876,8 +8999,8 @@ protected: const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); event.initialize(InputEvent::nextId(), injectedDeviceId, AINPUT_SOURCE_KEYBOARD, - ui::ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, - KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); + ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, + AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT; ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -8957,13 +9080,13 @@ protected: std::make_shared(); application->setDispatchingTimeout(100ms); mWindow = sp::make(application, mDispatcher, "TestWindow", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mWindow->setFrame(Rect(0, 0, 100, 100)); mWindow->setDispatchingTimeout(100ms); mWindow->setFocusable(true); // Set focused application. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); setFocusedWindow(mWindow); @@ -8991,50 +9114,55 @@ TEST_F_WITH_FLAGS( mDispatcher->setMinTimeBetweenUserActivityPokes(50ms); // First event of type TOUCH. Should poke. - notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(50)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH, + ui::LogicalDisplayId::DEFAULT}}); // 80ns > 50ns has passed since previous TOUCH event. Should poke. - notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(130)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH, + ui::LogicalDisplayId::DEFAULT}}); // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event). - notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, - milliseconds_to_nanoseconds(135)); + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, + ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(135)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER, + ui::LogicalDisplayId::DEFAULT}}); // Within 50ns of previous TOUCH event. Should NOT poke. - notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(140)); mFakePolicy->assertUserActivityNotPoked(); // Within 50ns of previous OTHER event. Should NOT poke. - notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, - milliseconds_to_nanoseconds(150)); + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, + ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(150)); mFakePolicy->assertUserActivityNotPoked(); // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke. // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source. - notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(160)); mFakePolicy->assertUserActivityNotPoked(); // 65ns > 50ns has passed since previous OTHER event. Should poke. - notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT, - milliseconds_to_nanoseconds(200)); + notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, + ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(200)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER, + ui::LogicalDisplayId::DEFAULT}}); // 170ns > 50ns has passed since previous TOUCH event. Should poke. - notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(300)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH, + ui::LogicalDisplayId::DEFAULT}}); // Assert that there's no more user activity poke event. mFakePolicy->assertUserActivityNotPoked(); @@ -9044,19 +9172,21 @@ TEST_F_WITH_FLAGS( InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed, REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, rate_limit_user_activity_poke_in_dispatcher))) { - notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(200)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH, + ui::LogicalDisplayId::DEFAULT}}); - notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(280)); mFakePolicy->assertUserActivityNotPoked(); - notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(340)); mFakePolicy->assertUserActivityPoked( - {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}}); + {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH, + ui::LogicalDisplayId::DEFAULT}}); } TEST_F_WITH_FLAGS( @@ -9065,10 +9195,12 @@ TEST_F_WITH_FLAGS( rate_limit_user_activity_poke_in_dispatcher))) { mDispatcher->setMinTimeBetweenUserActivityPokes(0ms); - notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20); + notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, + 20); mFakePolicy->assertUserActivityPoked(); - notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 30); + notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, + 30); mFakePolicy->assertUserActivityPoked(); } @@ -9078,16 +9210,16 @@ class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest { std::shared_ptr application = std::make_shared(); - mUnfocusedWindow = - sp::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT); + mUnfocusedWindow = sp::make(application, mDispatcher, "Top", + ui::LogicalDisplayId::DEFAULT); mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30)); - mFocusedWindow = - sp::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT); + mFocusedWindow = sp::make(application, mDispatcher, "Second", + ui::LogicalDisplayId::DEFAULT); mFocusedWindow->setFrame(Rect(50, 50, 100, 100)); // Set focused application. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mFocusedWindow->setFocusable(true); // Expect one focus window exist in display. @@ -9115,8 +9247,8 @@ protected: // the onPointerDownOutsideFocus callback. TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Success) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {20, 20})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {20, 20})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mUnfocusedWindow->consumeMotionDown(); @@ -9129,7 +9261,7 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Succe // onPointerDownOutsideFocus callback. TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPointerSource) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT, + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ui::LogicalDisplayId::DEFAULT, {20, 20})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mFocusedWindow->consumeMotionDown(); @@ -9142,9 +9274,9 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPo // have focus. Ensure no window received the onPointerDownOutsideFocus callback. TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT)) + injectKeyDownNoRepeat(*mDispatcher, ui::LogicalDisplayId::DEFAULT)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mFocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mFocusedWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertOnPointerDownWasNotCalled(); @@ -9155,8 +9287,8 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMo // onPointerDownOutsideFocus callback. TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - FOCUSED_WINDOW_TOUCH_POINT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_TOUCH_POINT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; mFocusedWindow->consumeMotionDown(); @@ -9175,7 +9307,8 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, NoFocusChangeFlag) { .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, event)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mUnfocusedWindow->consumeAnyMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertOnPointerDownWasNotCalled(); @@ -9192,10 +9325,10 @@ class InputDispatcherMultiWindowSameTokenTests : public InputDispatcherTest { std::shared_ptr application = std::make_shared(); mWindow1 = sp::make(application, mDispatcher, "Fake Window 1", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mWindow1->setFrame(Rect(0, 0, 100, 100)); - mWindow2 = mWindow1->clone(ADISPLAY_ID_DEFAULT); + mWindow2 = mWindow1->clone(ui::LogicalDisplayId::DEFAULT); mWindow2->setFrame(Rect(100, 100, 200, 200)); mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0}); @@ -9236,7 +9369,7 @@ protected: const std::vector& touchedPoints, std::vector expectedPoints) { mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, touchedPoints)); + ui::LogicalDisplayId::DEFAULT, touchedPoints)); consumeMotionEvent(touchedWindow, action, expectedPoints); } @@ -9383,14 +9516,14 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, TouchDoesNotSlipEvenIfSlippery) // Touch down in window 1 mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{50, 50}})); + ui::LogicalDisplayId::DEFAULT, {{50, 50}})); consumeMotionEvent(mWindow1, ACTION_DOWN, {{50, 50}}); // Move touch to be above window 2. Even though window 1 is slippery, touch should not slip. // That means the gesture should continue normally, without any ACTION_CANCEL or ACTION_DOWN // getting generated. mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {{150, 150}})); + ui::LogicalDisplayId::DEFAULT, {{150, 150}})); consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}}); } @@ -9425,13 +9558,13 @@ class InputDispatcherSingleWindowAnr : public InputDispatcherTest { mApplication = std::make_shared(); mApplication->setDispatchingTimeout(100ms); mWindow = sp::make(mApplication, mDispatcher, "TestWindow", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mWindow->setFrame(Rect(0, 0, 30, 30)); mWindow->setDispatchingTimeout(100ms); mWindow->setFocusable(true); // Set focused application. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); setFocusedWindow(mWindow); @@ -9462,8 +9595,8 @@ protected: } sp addSpyWindow() { - sp spy = - sp::make(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT); + sp spy = sp::make(mApplication, mDispatcher, "Spy", + ui::LogicalDisplayId::DEFAULT); spy->setTrustedOverlay(true); spy->setFocusable(false); spy->setSpy(true); @@ -9485,7 +9618,7 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenTouchIsConsumed_NoAnr) { // Send a regular key and respond, which should not cause an ANR. TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher)); - mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); } @@ -9496,15 +9629,16 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) { mWindow->consumeFocusEvent(false); InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, CONSUME_TIMEOUT_EVENT_EXPECTED, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE, + CONSUME_TIMEOUT_EVENT_EXPECTED, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Key will not go to window because we have no focused window. // The 'no focused window' ANR timer should start instead. // Now, the focused application goes away. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, nullptr); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, nullptr); // The key should get dropped and there should be no ANR. ASSERT_TRUE(mDispatcher->waitForIdle()); @@ -9516,8 +9650,8 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) { // So InputDispatcher will enqueue ACTION_CANCEL event as well. TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION)); const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); @@ -9526,7 +9660,7 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) { mWindow->finishEvent(*sequenceNum); mWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); } @@ -9550,8 +9684,8 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) { // taps on the window work as normal ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION)); ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown()); mDispatcher->waitForIdle(); mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -9560,8 +9694,8 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) { // We specify the injection timeout to be smaller than the application timeout, to ensure that // injection times out (instead of failing). const InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result); const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -9585,9 +9719,10 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count(); // Define a valid key down event that is stale (too old). - event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE, - INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A, - AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime); + event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, + /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, + eventTime); const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT; @@ -9609,7 +9744,7 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) { TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) { const std::chrono::duration appTimeout = 400ms; mApplication->setDispatchingTimeout(appTimeout); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication); mWindow->setFocusable(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0}); @@ -9621,8 +9756,9 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) const std::chrono::duration eventInjectionTimeout = 100ms; ASSERT_LT(eventInjectionTimeout, appTimeout); const InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, + eventInjectionTimeout, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result) << "result=" << ftl::enum_string(result); @@ -9670,20 +9806,20 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) { TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) { nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, WINDOW_LOCATION, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION, {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}, 500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime); // Now send ACTION_UP, with identical timestamp injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, WINDOW_LOCATION, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION, {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}, 500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime); // We have now sent down and up. Let's consume first event and then ANR on the second. - mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow); } @@ -9693,8 +9829,8 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) { sp spy = addSpyWindow(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION)); mWindow->consumeMotionDown(); const auto [sequenceNum, _] = spy->receiveEvent(); // ACTION_DOWN @@ -9704,7 +9840,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) { spy->finishEvent(*sequenceNum); spy->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid()); } @@ -9715,9 +9851,10 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey) sp spy = addSpyWindow(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT)); - mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); - ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher, ADISPLAY_ID_DEFAULT)); + injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT)); + mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, + injectKeyUp(*mDispatcher, ui::LogicalDisplayId::DEFAULT)); // Stuck on the ACTION_UP const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); @@ -9725,10 +9862,10 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey) // New tap will go to the spy window, but not to the window tapOnWindow(); - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); - spy->consumeMotionUp(ADISPLAY_ID_DEFAULT); + spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); - mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion + mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); // still the previous motion mDispatcher->waitForIdle(); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mWindow->assertNoEvents(); @@ -9741,8 +9878,8 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMoti sp spy = addSpyWindow(); tapOnWindow(); - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); - spy->consumeMotionUp(ADISPLAY_ID_DEFAULT); + spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); mWindow->consumeMotionDown(); // Stuck on the ACTION_UP @@ -9751,10 +9888,10 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMoti // New tap will go to the spy window, but not to the window tapOnWindow(); - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); - spy->consumeMotionUp(ADISPLAY_ID_DEFAULT); + spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); - mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion + mWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); // still the previous motion mDispatcher->waitForIdle(); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); mWindow->assertNoEvents(); @@ -9764,13 +9901,14 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMoti TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) { mDispatcher->setMonitorDispatchingTimeoutForTest(SPY_TIMEOUT); - FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT); + FakeMonitorReceiver monitor = + FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION)); - mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); const std::optional consumeSeq = monitor.receiveEvent(); ASSERT_TRUE(consumeSeq); @@ -9778,7 +9916,7 @@ TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) { MONITOR_PID); monitor.finishEvent(*consumeSeq); - monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT); + monitor.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(monitor.getToken(), MONITOR_PID); @@ -9817,8 +9955,8 @@ TEST_F(InputDispatcherSingleWindowAnr, SameWindow_CanReceiveAnrTwice) { // it. TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION)); const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT); mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow); @@ -9828,7 +9966,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) { // When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL mWindow->consumeMotionDown(); mWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); mWindow->assertNoEvents(); mDispatcher->waitForIdle(); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid()); @@ -9865,7 +10003,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) { std::this_thread::sleep_for(400ms); // if we wait long enough though, dispatcher will give up, and still send the key // to the focused window, even though we have not yet finished the motion event - mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); mWindow->finishEvent(*downSequenceNum); mWindow->finishEvent(*upSequenceNum); } @@ -9969,8 +10107,8 @@ TEST_F(InputDispatcherSingleWindowAnr, TwoGesturesWithAnr) { // So InputDispatcher will enqueue ACTION_CANCEL event as well. TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) { mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {WINDOW_LOCATION})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {WINDOW_LOCATION})); const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); @@ -9987,7 +10125,7 @@ TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) { mWindow->finishEvent(*sequenceNum); // The cancellation was generated when the window was removed, along with the focus event. mWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); mWindow->consumeFocusEvent(false); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt); @@ -9997,8 +10135,8 @@ TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) { // notified of the unresponsive window, then remove the app window. TEST_F(InputDispatcherSingleWindowAnr, AnrFollowedByWindowRemoval) { mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {WINDOW_LOCATION})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {WINDOW_LOCATION})); const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(sequenceNum); @@ -10011,7 +10149,7 @@ TEST_F(InputDispatcherSingleWindowAnr, AnrFollowedByWindowRemoval) { mWindow->finishEvent(*sequenceNum); // The cancellation was generated during the ANR, and the window lost focus when it was removed. mWindow->consumeMotionEvent( - AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT))); + AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT))); mWindow->consumeFocusEvent(false); ASSERT_TRUE(mDispatcher->waitForIdle()); // Since the window was removed, Dispatcher does not know the PID associated with the window @@ -10026,18 +10164,18 @@ class InputDispatcherMultiWindowAnr : public InputDispatcherTest { mApplication = std::make_shared(); mApplication->setDispatchingTimeout(100ms); mUnfocusedWindow = sp::make(mApplication, mDispatcher, "Unfocused", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30)); // Adding FLAG_WATCH_OUTSIDE_TOUCH to receive ACTION_OUTSIDE when another window is tapped mUnfocusedWindow->setWatchOutsideTouch(true); mFocusedWindow = sp::make(mApplication, mDispatcher, "Focused", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mFocusedWindow->setDispatchingTimeout(100ms); mFocusedWindow->setFrame(Rect(50, 50, 100, 100)); // Set focused application. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication); mFocusedWindow->setFocusable(true); // Expect one focus window exist in display. @@ -10069,11 +10207,11 @@ protected: private: void tap(const PointF& location) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - location)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, location)); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - location)); + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, location)); } }; @@ -10098,7 +10236,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) { .build())); mFocusedWindow->consumeMotionDown(); mFocusedWindow->consumeMotionUp(); - mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0); + mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0); // We consumed all events, so no ANR ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -10174,7 +10312,7 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events. TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { tapOnFocusedWindow(); - mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0); + mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0); // Receive the events, but don't respond const auto [downEventSequenceNum, downEvent] = mFocusedWindow->receiveEvent(); // ACTION_DOWN ASSERT_TRUE(downEventSequenceNum); @@ -10187,10 +10325,10 @@ TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { // Tap once again // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success ASSERT_EQ(InputEventInjectionResult::FAILED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - FOCUSED_WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION)); ASSERT_EQ(InputEventInjectionResult::FAILED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION)); // Unfocused window does not receive ACTION_OUTSIDE because the tapped window is not a // valid touch target @@ -10211,8 +10349,8 @@ TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) { // If you tap outside of all windows, there will not be ANR TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) { ASSERT_EQ(InputEventInjectionResult::FAILED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - LOCATION_OUTSIDE_ALL_WINDOWS)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, LOCATION_OUTSIDE_ALL_WINDOWS)); ASSERT_TRUE(mDispatcher->waitForIdle()); mFakePolicy->assertNotifyAnrWasNotCalled(); } @@ -10224,8 +10362,8 @@ TEST_F(InputDispatcherMultiWindowAnr, Window_CanBePaused) { {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::FAILED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - FOCUSED_WINDOW_LOCATION)); + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION)); std::this_thread::sleep_for(mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT)); ASSERT_TRUE(mDispatcher->waitForIdle()); @@ -10265,8 +10403,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // window even if motions are still being processed. InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); // Key will not be sent to the window, yet, because the window is still processing events @@ -10293,7 +10431,7 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // Now that all queues are cleared and no backlog in the connections, the key event // can finally go to the newly focused "mUnfocusedWindow". - mUnfocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mUnfocusedWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); mFocusedWindow->assertNoEvents(); mUnfocusedWindow->assertNoEvents(); mFakePolicy->assertNotifyAnrWasNotCalled(); @@ -10304,14 +10442,15 @@ TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) { // The other window should not be affected by that. TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) { // Touch Window 1 - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {FOCUSED_WINDOW_LOCATION})); - mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {FOCUSED_WINDOW_LOCATION})); + mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0); // Touch Window 2 mDispatcher->notifyMotion( - generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION})); const std::chrono::duration timeout = @@ -10357,7 +10496,7 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ std::shared_ptr focusedApplication = std::make_shared(); focusedApplication->setDispatchingTimeout(300ms); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, focusedApplication); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, focusedApplication); // The application that owns 'mFocusedWindow' and 'mUnfocusedWindow' is not focused. mFocusedWindow->setFocusable(false); @@ -10369,8 +10508,8 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ // 'focusedApplication' will get blamed if this timer completes. // Key will not be sent anywhere because we have no focused window. It will remain pending. InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); @@ -10385,9 +10524,9 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ std::this_thread::sleep_for(100ms); // Touch unfocused window. This should force the pending key to get dropped. - mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {UNFOCUSED_WINDOW_LOCATION})); + mDispatcher->notifyMotion( + generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {UNFOCUSED_WINDOW_LOCATION})); // We do not consume the motion right away, because that would require dispatcher to first // process (== drop) the key event, and by that time, ANR will be raised. @@ -10440,20 +10579,20 @@ TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvent mFakePolicy->setStaleEventTimeout(3000ms); sp navigationBar = sp::make(systemUiApplication, mDispatcher, "NavigationBar", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); navigationBar->setFocusable(false); navigationBar->setWatchOutsideTouch(true); navigationBar->setFrame(Rect(0, 0, 100, 100)); mApplication->setDispatchingTimeout(3000ms); // 'mApplication' is already focused, but we call it again here to make it explicit. - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication); std::shared_ptr anotherApplication = std::make_shared(); sp appWindow = sp::make(anotherApplication, mDispatcher, "Another window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); appWindow->setFocusable(false); appWindow->setFrame(Rect(100, 100, 200, 200)); @@ -10472,8 +10611,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvent // Key will not be sent anywhere because we have no focused window. It will remain pending. // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is. InputEventInjectionResult result = - injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); @@ -10482,8 +10621,8 @@ TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvent mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) .build()); - result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, - InputEventInjectionSync::NONE, + result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, /*allowKeyRepeat=*/false); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); @@ -10519,16 +10658,16 @@ class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest { InputDispatcherTest::SetUp(); mApplication = std::make_shared(); - mNoInputWindow = - sp::make(mApplication, mDispatcher, - "Window without input channel", ADISPLAY_ID_DEFAULT, - /*createInputChannel=*/false); + mNoInputWindow = sp::make(mApplication, mDispatcher, + "Window without input channel", + ui::LogicalDisplayId::DEFAULT, + /*createInputChannel=*/false); mNoInputWindow->setNoInputChannel(true); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); // It's perfectly valid for this window to not have an associated input channel mBottomWindow = sp::make(mApplication, mDispatcher, "Bottom window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mBottomWindow->setFrame(Rect(0, 0, 100, 100)); mDispatcher->onWindowInfosChanged( @@ -10545,8 +10684,8 @@ TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouc PointF touchedPoint = {10, 10}; mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchedPoint})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {touchedPoint})); mNoInputWindow->assertNoEvents(); // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have @@ -10563,7 +10702,7 @@ TEST_F(InputDispatcherMultiWindowOcclusionTests, NoInputChannelFeature_DropsTouchesWithValidChannel) { mNoInputWindow = sp::make(mApplication, mDispatcher, "Window with input channel and NO_INPUT_CHANNEL", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); mNoInputWindow->setNoInputChannel(true); mNoInputWindow->setFrame(Rect(0, 0, 100, 100)); @@ -10573,8 +10712,8 @@ TEST_F(InputDispatcherMultiWindowOcclusionTests, PointF touchedPoint = {10, 10}; mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {touchedPoint})); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {touchedPoint})); mNoInputWindow->assertNoEvents(); mBottomWindow->assertNoEvents(); @@ -10589,9 +10728,10 @@ protected: virtual void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); - mMirror = mWindow->clone(ADISPLAY_ID_DEFAULT); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); + mMirror = mWindow->clone(ui::LogicalDisplayId::DEFAULT); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp); mWindow->setFocusable(true); mMirror->setFocusable(true); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); @@ -10606,7 +10746,7 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, CanGetFocus) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID); } // A focused & mirrored window remains focused only if the window and its mirror are both @@ -10618,10 +10758,10 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAllWindowsFocusable) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID); mMirror->setFocusable(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); @@ -10643,20 +10783,20 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAnyWindowVisible) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID); mMirror->setVisible(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID); mWindow->setVisible(false); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); @@ -10677,20 +10817,20 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedWhileWindowsAlive) { mWindow->consumeFocusEvent(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyUp(ui::ADISPLAY_ID_NONE); + mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID); // single window is removed but the window token remains focused mDispatcher->onWindowInfosChanged({{*mMirror->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mMirror->consumeKeyDown(ui::ADISPLAY_ID_NONE); + mMirror->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mMirror->consumeKeyUp(ui::ADISPLAY_ID_NONE); + mMirror->consumeKeyUp(ui::LogicalDisplayId::INVALID); // Both windows are removed mDispatcher->onWindowInfosChanged({{}, {}, 0, 0}); @@ -10712,7 +10852,7 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, DeferFocusWhenInvisible) { // Injected key goes to pending queue. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, - ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE)); + ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE)); mMirror->setVisible(true); mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0}); @@ -10720,7 +10860,7 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, DeferFocusWhenInvisible) { // window gets focused mWindow->consumeFocusEvent(true); // window gets the pending key event - mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); } class InputDispatcherPointerCaptureTests : public InputDispatcherTest { @@ -10732,13 +10872,14 @@ protected: void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); mWindow->setFocusable(true); - mSecondWindow = - sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); + mSecondWindow = sp::make(mApp, mDispatcher, "TestWindow2", + ui::LogicalDisplayId::DEFAULT); mSecondWindow->setFocusable(true); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp); mDispatcher->onWindowInfosChanged( {{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0}); @@ -10977,7 +11118,7 @@ protected: sp getWindow(gui::Uid uid, std::string name) { std::shared_ptr app = std::make_shared(); sp window = - sp::make(app, mDispatcher, name, ADISPLAY_ID_DEFAULT); + sp::make(app, mDispatcher, name, ui::LogicalDisplayId::DEFAULT); // Generate an arbitrary PID based on the UID window->setOwnerInfo(gui::Pid{static_cast(1777 + (uid.val() % 10000))}, uid); return window; @@ -10985,8 +11126,8 @@ protected: void touch(const std::vector& points = {PointF{100, 200}}) { mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - points)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, points)); } }; @@ -11351,20 +11492,21 @@ protected: void SetUp() override { InputDispatcherTest::SetUp(); mApp = std::make_shared(); - mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); mWindow->setFrame(Rect(0, 0, 100, 100)); - mSecondWindow = - sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); + mSecondWindow = sp::make(mApp, mDispatcher, "TestWindow2", + ui::LogicalDisplayId::DEFAULT); mSecondWindow->setFrame(Rect(100, 0, 200, 100)); - mSpyWindow = - sp::make(mApp, mDispatcher, "SpyWindow", ADISPLAY_ID_DEFAULT); + mSpyWindow = sp::make(mApp, mDispatcher, "SpyWindow", + ui::LogicalDisplayId::DEFAULT); mSpyWindow->setSpy(true); mSpyWindow->setTrustedOverlay(true); mSpyWindow->setFrame(Rect(0, 0, 200, 100)); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp); mDispatcher->onWindowInfosChanged( {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, @@ -11377,7 +11519,7 @@ protected: case AINPUT_SOURCE_TOUCHSCREEN: ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {50, 50})) + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; break; case AINPUT_SOURCE_STYLUS: @@ -11409,9 +11551,9 @@ protected: } // Window should receive motion event. - mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); // Spy window should also receive motion event - mSpyWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + mSpyWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } // Start performing drag, we will create a drag window and transfer touch to it. @@ -11423,8 +11565,8 @@ protected: } // The drag window covers the entire display - mDragWindow = - sp::make(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT); + mDragWindow = sp::make(mApp, mDispatcher, "DragWindow", + ui::LogicalDisplayId::DEFAULT); mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}}); mDispatcher->onWindowInfosChanged({{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()}, @@ -11438,7 +11580,8 @@ protected: /*isDragDrop=*/true); if (transferred) { mWindow->consumeMotionCancel(); - mDragWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); } return transferred; } @@ -11450,35 +11593,38 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) { // Move on window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {50, 50})) + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); // Move to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {150, 50})) + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); // Move back to original window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {50, 50})) + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->consumeDragEvent(true, -50, 50); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -11513,27 +11659,29 @@ TEST_F(InputDispatcherDragTests, DragAndDrop) { // Move on window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {50, 50})) + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); // Move to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {150, 50})) + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); // drop to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -11595,7 +11743,8 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -11607,7 +11756,8 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); @@ -11620,7 +11770,7 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) { .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -11636,27 +11786,29 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) { // Move on window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {50, 50})) + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); // Move to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {150, 50})) + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->assertNoEvents(); // drop to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, nullptr); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -11667,14 +11819,14 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) { mWindow->setPreventSplitting(true); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50)) @@ -11692,16 +11844,16 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) { TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { // First down on second window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {150, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mSecondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); + mSecondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); // Second down on first window. const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) @@ -11710,8 +11862,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT); - mSecondWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT); + mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + mSecondWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT); // Perform drag and drop from first window. ASSERT_TRUE(startDrag(false)); @@ -11726,7 +11878,8 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)); - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->consumeMotionMove(); @@ -11740,7 +11893,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)); - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->consumeMotionMove(); @@ -11779,27 +11932,29 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) { // Move on window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {50, 50})) + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); // Move to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {150, 50})) + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); // drop to another window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -11817,7 +11972,8 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { .y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(false, 50, 50); mSecondWindow->assertNoEvents(); @@ -11831,7 +11987,8 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { .y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mWindow->consumeDragEvent(true, 150, 50); mSecondWindow->consumeDragEvent(false, 50, 50); @@ -11845,7 +12002,7 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { .y(50)) .build())) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken()); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); @@ -11858,8 +12015,8 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) { TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) { // Down on second window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {150, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {150, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionDown()); @@ -11868,7 +12025,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) { // Down on first window const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) .build(); @@ -11886,7 +12043,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) { // Trigger cancel mDispatcher->cancelCurrentTouch(); ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionCancel()); - ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, + ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)); ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionCancel()); @@ -11899,14 +12056,14 @@ TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) { // Inject a simple gesture, ensure dispatcher not crashed ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - PointF{50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, PointF{50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown()); const MotionEvent moveEvent = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50)) .build(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, @@ -11916,7 +12073,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) { ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionMove()); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionUp()); @@ -11926,7 +12083,7 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWithHoveringPointer) { // Start hovering over the window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE, - ADISPLAY_ID_DEFAULT, {50, 50})); + ui::LogicalDisplayId::DEFAULT, {50, 50})); ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER))); ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER))); @@ -11939,23 +12096,25 @@ class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {}; TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) { std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Test window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Test window", + ui::LogicalDisplayId::DEFAULT); window->setDropInput(true); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); window->assertNoEvents(); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); mDispatcher->waitForIdle(); window->assertNoEvents(); @@ -11963,12 +12122,13 @@ TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) { window->setDropInput(false); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); - window->consumeKeyUp(ADISPLAY_ID_DEFAULT); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT)); + window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); window->assertNoEvents(); } @@ -11977,16 +12137,17 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) { std::make_shared(); sp obscuringWindow = sp::make(obscuringApplication, mDispatcher, "obscuringWindow", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); obscuringWindow->setFrame(Rect(0, 0, 50, 50)); obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111}); obscuringWindow->setTouchable(false); std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Test window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Test window", + ui::LogicalDisplayId::DEFAULT); window->setDropInputIfObscured(true); window->setOwnerInfo(gui::Pid{222}, gui::Uid{222}); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); mDispatcher->onWindowInfosChanged( {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); @@ -11994,13 +12155,14 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) { window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); window->assertNoEvents(); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); window->assertNoEvents(); // With the flag cleared, the window should get input @@ -12008,12 +12170,14 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) { mDispatcher->onWindowInfosChanged( {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); - window->consumeKeyUp(ADISPLAY_ID_DEFAULT); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT)); + window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED); window->assertNoEvents(); } @@ -12022,16 +12186,17 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { std::make_shared(); sp obscuringWindow = sp::make(obscuringApplication, mDispatcher, "obscuringWindow", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); obscuringWindow->setFrame(Rect(0, 0, 50, 50)); obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111}); obscuringWindow->setTouchable(false); std::shared_ptr application = std::make_shared(); - sp window = sp::make(application, mDispatcher, - "Test window", ADISPLAY_ID_DEFAULT); + sp window = + sp::make(application, mDispatcher, "Test window", + ui::LogicalDisplayId::DEFAULT); window->setDropInputIfObscured(true); window->setOwnerInfo(gui::Pid{222}, gui::Uid{222}); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); window->setFocusable(true); mDispatcher->onWindowInfosChanged( {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0}); @@ -12039,25 +12204,27 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) { window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); // With the flag set, window should not get any input - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT)); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); window->assertNoEvents(); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); window->assertNoEvents(); // When the window is no longer obscured because it went on top, it should get input mDispatcher->onWindowInfosChanged( {{*window->getInfo(), *obscuringWindow->getInfo()}, {}, 0, 0}); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT)); - window->consumeKeyUp(ADISPLAY_ID_DEFAULT); + mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT)); + window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, - AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)); - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); window->assertNoEvents(); } @@ -12074,18 +12241,19 @@ protected: mApp = std::make_shared(); mSecondaryApp = std::make_shared(); - mWindow = sp::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT); + mWindow = sp::make(mApp, mDispatcher, "TestWindow", + ui::LogicalDisplayId::DEFAULT); mWindow->setFocusable(true); setFocusedWindow(mWindow); - mSecondWindow = - sp::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT); + mSecondWindow = sp::make(mApp, mDispatcher, "TestWindow2", + ui::LogicalDisplayId::DEFAULT); mSecondWindow->setFocusable(true); mThirdWindow = sp::make(mSecondaryApp, mDispatcher, "TestWindow3_SecondaryDisplay", SECOND_DISPLAY_ID); mThirdWindow->setFocusable(true); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp); mDispatcher->onWindowInfosChanged( {{*mWindow->getInfo(), *mSecondWindow->getInfo(), *mThirdWindow->getInfo()}, {}, @@ -12096,7 +12264,8 @@ protected: // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode. if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID, - WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) { + WINDOW_UID, /*hasPermission=*/true, + ui::LogicalDisplayId::DEFAULT)) { mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode); mThirdWindow->assertNoEvents(); @@ -12115,7 +12284,7 @@ protected: void changeAndVerifyTouchModeInMainDisplayOnly(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission) { ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); mWindow->consumeTouchModeEvent(inTouchMode); mSecondWindow->consumeTouchModeEvent(inTouchMode); mThirdWindow->assertNoEvents(); @@ -12136,7 +12305,7 @@ TEST_F(InputDispatcherTouchModeChangedTests, NonFocusedWindowOwnerCannotChangeTo mWindow->setOwnerInfo(gui::Pid::INVALID, gui::Uid::INVALID); ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid, ownerUid, /*hasPermission=*/false, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -12154,7 +12323,8 @@ TEST_F(InputDispatcherTouchModeChangedTests, EventIsNotGeneratedIfNotChangingTou const WindowInfo& windowInfo = *mWindow->getInfo(); ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)); + /*hasPermission=*/true, + ui::LogicalDisplayId::DEFAULT)); mWindow->assertNoEvents(); mSecondWindow->assertNoEvents(); } @@ -12172,9 +12342,9 @@ TEST_F(InputDispatcherTouchModeChangedTests, ChangeTouchOnSecondaryDisplayOnly) TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) { // Interact with the window first. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT)) + injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); + mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); // Then remove focus. mWindow->setFocusable(false); @@ -12184,7 +12354,8 @@ TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInt const WindowInfo& windowInfo = *mWindow->getInfo(); ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode, windowInfo.ownerPid, windowInfo.ownerUid, - /*hasPermission=*/false, ADISPLAY_ID_DEFAULT)); + /*hasPermission=*/false, + ui::LogicalDisplayId::DEFAULT)); } class InputDispatcherSpyWindowTest : public InputDispatcherTest { @@ -12194,8 +12365,9 @@ public: std::make_shared(); std::string name = "Fake Spy "; name += std::to_string(mSpyCount++); - sp spy = sp::make(application, mDispatcher, - name.c_str(), ADISPLAY_ID_DEFAULT); + sp spy = + sp::make(application, mDispatcher, name.c_str(), + ui::LogicalDisplayId::DEFAULT); spy->setSpy(true); spy->setTrustedOverlay(true); return spy; @@ -12206,7 +12378,7 @@ public: std::make_shared(); sp window = sp::make(application, mDispatcher, "Fake Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); return window; } @@ -12237,9 +12409,10 @@ TEST_F(InputDispatcherSpyWindowTest, NoForegroundWindow) { mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); + spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); } /** @@ -12277,7 +12450,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) { } ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; std::vector eventOrder; @@ -12315,9 +12489,10 @@ TEST_F(InputDispatcherSpyWindowTest, NotTouchable) { mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); spy->assertNoEvents(); } @@ -12334,20 +12509,22 @@ TEST_F(InputDispatcherSpyWindowTest, TouchableRegion) { // Inject an event outside the spy window's touchable region. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spy->assertNoEvents(); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionUp(); spy->assertNoEvents(); // Inject an event inside the spy window's touchable region. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {5, 10})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {5, 10})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spy->consumeMotionDown(); @@ -12368,8 +12545,8 @@ TEST_F(InputDispatcherSpyWindowTest, WatchOutsideTouches) { // Inject an event outside the spy window's frame and touchable region. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spy->consumeMotionOutsideWithZeroedCoords(); @@ -12390,8 +12567,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) { {{*spy->getInfo(), *windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; windowLeft->consumeMotionDown(); spy->consumeMotionDown(); @@ -12422,8 +12599,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) { mDispatcher->onWindowInfosChanged({{*spyRight->getInfo(), *window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {50, 50})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {50, 50})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spyRight->assertNoEvents(); @@ -12459,16 +12636,16 @@ TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { // First finger down, no window touched. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); + spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); window->assertNoEvents(); // Second finger down on window, the window should receive touch down. const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) @@ -12478,7 +12655,7 @@ TEST_F(InputDispatcherSpyWindowTest, SplitIfNoForegroundWindowTouched) { InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); spy->consumeMotionPointerDown(/*pointerIndex=*/1); } @@ -12497,11 +12674,11 @@ TEST_F(InputDispatcherSpyWindowTest, UnfocusableSpyDoesNotReceiveKeyEvents) { ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeKeyDown(ui::ADISPLAY_ID_NONE); + window->consumeKeyDown(ui::LogicalDisplayId::INVALID); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher)) << "Inject key event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeKeyUp(ui::ADISPLAY_ID_NONE); + window->consumeKeyUp(ui::LogicalDisplayId::INVALID); spy->assertNoEvents(); } @@ -12520,7 +12697,8 @@ TEST_F(InputDispatcherPilferPointersTest, PilferPointers) { {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spy1->consumeMotionDown(); @@ -12535,7 +12713,7 @@ TEST_F(InputDispatcherPilferPointersTest, PilferPointers) { // The rest of the gesture should only be sent to the second spy window. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)) + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; spy2->consumeMotionMove(); spy1->assertNoEvents(); @@ -12552,19 +12730,21 @@ TEST_F(InputDispatcherPilferPointersTest, CanPilferAfterWindowIsRemovedMidStream mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - window->consumeMotionDown(ADISPLAY_ID_DEFAULT); - spy->consumeMotionDown(ADISPLAY_ID_DEFAULT); + window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); + spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT); window->releaseChannel(); EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT)) + injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - spy->consumeMotionUp(ADISPLAY_ID_DEFAULT); + spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); } /** @@ -12579,8 +12759,8 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) // First finger down on the window and the spy. ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {100, 200})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {100, 200})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; spy->consumeMotionDown(); window->consumeMotionDown(); @@ -12592,7 +12772,7 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) // Second finger down on the window and spy, but the window should not receive the pointer down. const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) @@ -12607,7 +12787,7 @@ TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) // Third finger goes down outside all windows, so injection should fail. const MotionEvent thirdFingerDownEvent = MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) @@ -12635,15 +12815,15 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { // First finger down on the window only ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {150, 150})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {150, 150})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); // Second finger down on the spy and window const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10)) @@ -12658,7 +12838,7 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { // Third finger down on the spy and window const MotionEvent thirdFingerDownEvent = MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10)) @@ -12673,8 +12853,10 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { // Spy window pilfers the pointers. EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); - window->consumeMotionPointerUp(/*idx=*/2, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED); - window->consumeMotionPointerUp(/*idx=*/1, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED); + window->consumeMotionPointerUp(/*idx=*/2, ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_CANCELED); + window->consumeMotionPointerUp(/*idx=*/1, ui::LogicalDisplayId::DEFAULT, + AMOTION_EVENT_FLAG_CANCELED); spy->assertNoEvents(); window->assertNoEvents(); @@ -12695,8 +12877,8 @@ TEST_F(InputDispatcherPilferPointersTest, PilferAllRequiredPointers) { // First finger down on both spy and window ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {10, 10})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {10, 10})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spy->consumeMotionDown(); @@ -12704,7 +12886,7 @@ TEST_F(InputDispatcherPilferPointersTest, PilferAllRequiredPointers) { // Second finger down on the spy and window const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50)) @@ -12738,8 +12920,8 @@ TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { // First finger down on both window and spy ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, - injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, - {10, 10})) + injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, + ui::LogicalDisplayId::DEFAULT, {10, 10})) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; window->consumeMotionDown(); spy->consumeMotionDown(); @@ -12751,7 +12933,7 @@ TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) { // Second finger down on the window only const MotionEvent secondFingerDownEvent = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .displayId(ADISPLAY_ID_DEFAULT) + .displayId(ui::LogicalDisplayId::DEFAULT) .eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10)) .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150)) @@ -12936,9 +13118,9 @@ public: std::pair, sp> setupStylusOverlayScenario() { std::shared_ptr overlayApplication = std::make_shared(); - sp overlay = - sp::make(overlayApplication, mDispatcher, - "Stylus interceptor window", ADISPLAY_ID_DEFAULT); + sp overlay = sp::make(overlayApplication, mDispatcher, + "Stylus interceptor window", + ui::LogicalDisplayId::DEFAULT); overlay->setFocusable(false); overlay->setOwnerInfo(gui::Pid{111}, gui::Uid{111}); overlay->setTouchable(false); @@ -12949,11 +13131,11 @@ public: std::make_shared(); sp window = sp::make(application, mDispatcher, "Application window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); window->setFocusable(true); window->setOwnerInfo(gui::Pid{222}, gui::Uid{222}); - mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application); + mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application); mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true); @@ -12963,13 +13145,13 @@ public: void sendFingerEvent(int32_t action) { mDispatcher->notifyMotion( generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{20, 20}})); + ui::LogicalDisplayId::DEFAULT, {PointF{20, 20}})); } void sendStylusEvent(int32_t action) { NotifyMotionArgs motionArgs = generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, - ADISPLAY_ID_DEFAULT, {PointF{30, 40}}); + ui::LogicalDisplayId::DEFAULT, {PointF{30, 40}}); motionArgs.pointerProperties[0].toolType = ToolType::STYLUS; mDispatcher->notifyMotion(motionArgs); } @@ -13073,7 +13255,7 @@ struct User { InputEventInjectionResult injectTargetedMotion(int32_t action) const { return injectMotionEvent(*mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT, {100, 200}, + ui::LogicalDisplayId::DEFAULT, {100, 200}, {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT, @@ -13082,7 +13264,7 @@ struct User { InputEventInjectionResult injectTargetedKey(int32_t action) const { return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0, - ui::ADISPLAY_ID_NONE, + ui::LogicalDisplayId::INVALID, InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid}, mPolicyFlags); @@ -13091,8 +13273,9 @@ struct User { sp createWindow(const char* name) const { std::shared_ptr overlayApplication = std::make_shared(); - sp window = sp::make(overlayApplication, mDispatcher, - name, ADISPLAY_ID_DEFAULT); + sp window = + sp::make(overlayApplication, mDispatcher, name, + ui::LogicalDisplayId::DEFAULT); window->setOwnerInfo(mPid, mUid); return window; } @@ -13114,7 +13297,7 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) { EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, owner.injectTargetedKey(AKEY_EVENT_ACTION_DOWN)); - window->consumeKeyDown(ui::ADISPLAY_ID_NONE); + window->consumeKeyDown(ui::LogicalDisplayId::INVALID); } TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) { @@ -13179,7 +13362,7 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTarget // A user that has injection permission can inject into any window. EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, - ADISPLAY_ID_DEFAULT)); + ui::LogicalDisplayId::DEFAULT)); randosSpy->consumeMotionDown(); window->consumeMotionDown(); @@ -13187,7 +13370,7 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTarget randosSpy->consumeFocusEvent(true); EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher)); - randosSpy->consumeKeyDown(ui::ADISPLAY_ID_NONE); + randosSpy->consumeKeyDown(ui::LogicalDisplayId::INVALID); window->assertNoEvents(); } @@ -13214,13 +13397,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) { std::shared_ptr application = std::make_shared(); sp left = sp::make(application, mDispatcher, "Left Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); left->setFrame(Rect(0, 0, 100, 100)); - sp right = sp::make(application, mDispatcher, - "Right Window", ADISPLAY_ID_DEFAULT); + sp right = + sp::make(application, mDispatcher, "Right Window", + ui::LogicalDisplayId::DEFAULT); right->setFrame(Rect(100, 0, 200, 100)); - sp spy = - sp::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT); + sp spy = sp::make(application, mDispatcher, "Spy Window", + ui::LogicalDisplayId::DEFAULT); spy->setFrame(Rect(0, 0, 200, 100)); spy->setTrustedOverlay(true); spy->setSpy(true); @@ -13237,11 +13421,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) { left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Hover move to the right window. @@ -13254,11 +13441,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) { right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Stop hovering. @@ -13270,11 +13460,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) { right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); } @@ -13282,13 +13475,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { std::shared_ptr application = std::make_shared(); sp left = sp::make(application, mDispatcher, "Left Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); left->setFrame(Rect(0, 0, 100, 100)); - sp right = sp::make(application, mDispatcher, - "Right Window", ADISPLAY_ID_DEFAULT); + sp right = + sp::make(application, mDispatcher, "Right Window", + ui::LogicalDisplayId::DEFAULT); right->setFrame(Rect(100, 0, 200, 100)); - sp spy = - sp::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT); + sp spy = sp::make(application, mDispatcher, "Spy Window", + ui::LogicalDisplayId::DEFAULT); spy->setFrame(Rect(0, 0, 200, 100)); spy->setTrustedOverlay(true); spy->setSpy(true); @@ -13305,11 +13499,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { left->consumeMotionDown(); spy->consumeMotionDown(); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Second pointer down on right window. @@ -13323,17 +13520,23 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { right->consumeMotionDown(); spy->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/1)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/1)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/1)); // Second pointer up. @@ -13347,17 +13550,23 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { right->consumeMotionUp(); spy->consumeMotionEvent(WithMotionAction(POINTER_1_UP)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/1)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/1)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/1)); // First pointer up. @@ -13369,11 +13578,14 @@ TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) { left->consumeMotionUp(); spy->consumeMotionUp(); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); } @@ -13382,17 +13594,20 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_le std::shared_ptr application = std::make_shared(); sp left = sp::make(application, mDispatcher, "Left Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); left->setFrame(Rect(0, 0, 100, 100)); - sp right = sp::make(application, mDispatcher, - "Right Window", ADISPLAY_ID_DEFAULT); + sp right = + sp::make(application, mDispatcher, "Right Window", + ui::LogicalDisplayId::DEFAULT); right->setFrame(Rect(100, 0, 200, 100)); mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0}); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Hover move into the window. @@ -13406,7 +13621,8 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_le left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Move the mouse with another device. This cancels the hovering pointer from the first device. @@ -13423,9 +13639,10 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_le // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets // a HOVER_EXIT from the first device. - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, SECOND_DEVICE_ID, /*pointerId=*/0)); @@ -13441,9 +13658,10 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_le left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT)); right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, SECOND_DEVICE_ID, /*pointerId=*/0)); } @@ -13457,17 +13675,20 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { std::shared_ptr application = std::make_shared(); sp left = sp::make(application, mDispatcher, "Left Window", - ADISPLAY_ID_DEFAULT); + ui::LogicalDisplayId::DEFAULT); left->setFrame(Rect(0, 0, 100, 100)); - sp right = sp::make(application, mDispatcher, - "Right Window", ADISPLAY_ID_DEFAULT); + sp right = + sp::make(application, mDispatcher, "Right Window", + ui::LogicalDisplayId::DEFAULT); right->setFrame(Rect(100, 0, 200, 100)); mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0}); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Hover move into the window. @@ -13481,7 +13702,8 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); // Move the mouse with another device @@ -13496,9 +13718,10 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets // a HOVER_EXIT from the first device. - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, SECOND_DEVICE_ID, /*pointerId=*/0)); @@ -13513,9 +13736,10 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { .build()); right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)); - ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID, + ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, + DEVICE_ID, /*pointerId=*/0)); - ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, + ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT, SECOND_DEVICE_ID, /*pointerId=*/0)); } diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp index 15565326b1..bdf156c49a 100644 --- a/services/inputflinger/tests/InputProcessorConverter_test.cpp +++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp @@ -38,7 +38,7 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5); static constexpr nsecs_t downTime = 2; NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, - /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::ADISPLAY_ID_DEFAULT, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp index 5606a91a0b..f7e5e6783b 100644 --- a/services/inputflinger/tests/InputProcessor_test.cpp +++ b/services/inputflinger/tests/InputProcessor_test.cpp @@ -44,7 +44,7 @@ static NotifyMotionArgs generateBasicMotionArgs() { coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1); static constexpr nsecs_t downTime = 2; NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2, - /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::ADISPLAY_ID_DEFAULT, + /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, @@ -80,7 +80,7 @@ TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) { TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) { // Create a basic key event and send to processor NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3, - AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, + AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/0, AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, AMETA_NONE, /*downTime=*/6); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index fcc52a8f31..a0abb61cdc 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -59,11 +59,10 @@ using std::chrono_literals::operator""ms; using std::chrono_literals::operator""s; // Arbitrary display properties. -static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; static const std::string DISPLAY_UNIQUE_ID = "local:1"; static constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1}; -static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2"; static constexpr int32_t DISPLAY_WIDTH = 480; static constexpr int32_t DISPLAY_HEIGHT = 800; static constexpr ui::LogicalDisplayId VIRTUAL_DISPLAY_ID = ui::LogicalDisplayId{1}; @@ -521,12 +520,12 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { const std::string uniqueId1 = "uniqueId1"; const std::string uniqueId2 = "uniqueId2"; constexpr ui::LogicalDisplayId nonDefaultDisplayId = ui::LogicalDisplayId{2}; - ASSERT_NE(nonDefaultDisplayId, ui::ADISPLAY_ID_DEFAULT) - << "Test display ID should not be ui::ADISPLAY_ID_DEFAULT "; + ASSERT_NE(nonDefaultDisplayId, ui::LogicalDisplayId::DEFAULT) + << "Test display ID should not be ui::LogicalDisplayId::DEFAULT "; // Add the default display first and ensure it gets returned. mFakePolicy->clearViewports(); - mFakePolicy->addDisplayViewport(ui::ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, + mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, ViewportType::INTERNAL); mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, @@ -536,7 +535,7 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { std::optional viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); ASSERT_TRUE(viewport); - ASSERT_EQ(ui::ADISPLAY_ID_DEFAULT, viewport->displayId); + ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId); ASSERT_EQ(ViewportType::INTERNAL, viewport->type); // Add the default display second to make sure order doesn't matter. @@ -544,13 +543,13 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) { mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT, ViewportType::INTERNAL); - mFakePolicy->addDisplayViewport(ui::ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, + mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT, ViewportType::INTERNAL); viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL); ASSERT_TRUE(viewport); - ASSERT_EQ(ui::ADISPLAY_ID_DEFAULT, viewport->displayId); + ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId); ASSERT_EQ(ViewportType::INTERNAL, viewport->type); } @@ -3244,7 +3243,7 @@ protected: void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode, - ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_NONE); + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID); }; /* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the @@ -3579,19 +3578,19 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware AINPUT_KEYBOARD_TYPE_ALPHABETIC); NotifyKeyArgs args; - // Display id should be ADISPLAY_ID_NONE without any display configuration. + // Display id should be LogicalDisplayId::INVALID without any display configuration. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(ui::ADISPLAY_ID_NONE, args.displayId); + ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId); prepareDisplay(ui::ROTATION_0); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args)); - ASSERT_EQ(ui::ADISPLAY_ID_NONE, args.displayId); + ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId); } TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { @@ -3605,7 +3604,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { AINPUT_KEYBOARD_TYPE_ALPHABETIC); NotifyKeyArgs args; - // Display id should be ADISPLAY_ID_NONE without any display configuration. + // Display id should be LogicalDisplayId::INVALID without any display configuration. // ^--- already checked by the previous test setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, @@ -8675,7 +8674,7 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) { ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action); - ASSERT_EQ(ui::ADISPLAY_ID_NONE, motionArgs.displayId); + ASSERT_EQ(ui::LogicalDisplayId::INVALID, motionArgs.displayId); } /** @@ -9572,7 +9571,7 @@ TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) { processPosition(mapper, 100, 100); processSync(mapper); ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs)); - ASSERT_EQ(ui::ADISPLAY_ID_DEFAULT, motionArgs.displayId); + ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, motionArgs.displayId); // Expect the event to be sent to the external viewport if it is present. prepareSecondaryDisplay(ViewportType::EXTERNAL); diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp index dfbbce3b1a..617d67f34d 100644 --- a/services/inputflinger/tests/InputTracingTest.cpp +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -41,7 +41,7 @@ using perfetto::protos::pbzero::AndroidInputEventConfig; namespace { -constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; // Ensure common actions are interchangeable between keys and motions for convenience. static_assert(static_cast(AMOTION_EVENT_ACTION_DOWN) == diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp index 01fd03e874..4fcffddee2 100644 --- a/services/inputflinger/tests/LatencyTracker_test.cpp +++ b/services/inputflinger/tests/LatencyTracker_test.cpp @@ -43,7 +43,7 @@ static InputDeviceInfo generateTestDeviceInfo(uint16_t vendorId, uint16_t produc identifier.product = productId; auto info = InputDeviceInfo(); info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "Test Device", - /*isExternal=*/false, /*hasMic=*/false, ui::ADISPLAY_ID_NONE); + /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID); return info; } diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp index 437020f48a..b5f897154b 100644 --- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -35,7 +35,7 @@ using testing::Return; using testing::SetArgPointee; using testing::VariantWith; -static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; static constexpr int32_t DISPLAY_WIDTH = 480; static constexpr int32_t DISPLAY_HEIGHT = 800; static constexpr std::optional NO_PORT = std::nullopt; // no physical port is specified diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 33e7277034..1689b33fe5 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -203,14 +203,16 @@ TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) { TEST_F(PointerChoreographerTest, WhenMouseIsAddedCreatesPointerController) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerCreated(ControllerType::MOUSE); } TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); // Remove the mouse. @@ -221,7 +223,8 @@ TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) { TEST_F(PointerChoreographerTest, WhenKeyboardIsAddedDoesNotCreatePointerController) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerNotCreated(); } @@ -256,7 +259,8 @@ TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) { // the PointerController. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertViewportSet(DISPLAY_ID); ASSERT_TRUE(pc->isPointerShown()); @@ -269,7 +273,8 @@ TEST_F(PointerChoreographerTest, mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); firstDisplayPc->assertViewportSet(DISPLAY_ID); ASSERT_TRUE(firstDisplayPc->isPointerShown()); @@ -289,7 +294,8 @@ TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); @@ -299,7 +305,8 @@ TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterCallsNotifyPointerDisplay mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotNotified(); @@ -312,12 +319,13 @@ TEST_F(PointerChoreographerTest, WhenMouseIsRemovedCallsNotifyPointerDisplayIdCh mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); - assertPointerDisplayIdNotified(ui::ADISPLAY_ID_NONE); + assertPointerDisplayIdNotified(ui::LogicalDisplayId::INVALID); assertPointerControllerRemoved(pc); } @@ -329,7 +337,8 @@ TEST_F(PointerChoreographerTest, WhenDefaultMouseDisplayChangesCallsNotifyPointe mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); @@ -346,7 +355,8 @@ TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -358,7 +368,7 @@ TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) { MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(MOUSE_POINTER) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); // Check that the PointerController updated the position and the pointer is shown. @@ -375,7 +385,8 @@ TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -390,7 +401,7 @@ TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) { MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(absoluteMousePointer) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); // Check that the PointerController updated the position and the pointer is shown. @@ -412,7 +423,7 @@ TEST_F(PointerChoreographerTest, // Add two devices, one unassociated and the other associated with non-default mouse display. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId()); @@ -449,7 +460,8 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -460,7 +472,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { mChoreographer.notifyInputDevicesChanged( {/*id=*/1, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); mChoreographer.notifyPointerCaptureChanged( NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC), PointerCaptureRequest(/*window=*/sp::make(), @@ -475,7 +487,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10) .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20)) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); // Check that there's no update on the PointerController. @@ -485,7 +497,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) { // Check x-y coordinates, displayId and cursor position are not changed. mTestListener.assertNotifyMotionWasCalled( AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), - WithDisplayId(ui::ADISPLAY_ID_NONE), + WithDisplayId(ui::LogicalDisplayId::INVALID), WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION))); } @@ -495,7 +507,8 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); ASSERT_TRUE(pc->isPointerShown()); @@ -515,7 +528,8 @@ TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) { // A mouse is connected, and the pointer is shown. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_TRUE(pc->isPointerShown()); @@ -525,9 +539,9 @@ TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) { // Add a second mouse is added, the pointer is shown again. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); ASSERT_TRUE(pc->isPointerShown()); // One of the mice is removed, and it does not cause the mouse pointer to fade, because @@ -535,7 +549,7 @@ TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerNotRemoved(pc); ASSERT_TRUE(pc->isPointerShown()); @@ -549,7 +563,8 @@ TEST_F(PointerChoreographerTest, UnrelatedChangeDoesNotUnfadePointer) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_TRUE(pc->isPointerShown()); @@ -559,7 +574,7 @@ TEST_F(PointerChoreographerTest, UnrelatedChangeDoesNotUnfadePointer) { // Adding a touchscreen device does not unfade the mouse pointer. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, DISPLAY_ID)}}); @@ -576,7 +591,7 @@ TEST_F(PointerChoreographerTest, DisabledMouseConnected) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); InputDeviceInfo mouseDeviceInfo = - generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); + generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID); // Disable this mouse device. mouseDeviceInfo.setEnabled(false); mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}}); @@ -589,7 +604,7 @@ TEST_F(PointerChoreographerTest, MouseDeviceDisableLater) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); InputDeviceInfo mouseDeviceInfo = - generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); + generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID); mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}}); @@ -608,14 +623,16 @@ TEST_F(PointerChoreographerTest, MultipleEnabledAndDisabledMiceConnectionAndRemo mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); InputDeviceInfo disabledMouseDeviceInfo = - generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); + generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID); disabledMouseDeviceInfo.setEnabled(false); InputDeviceInfo enabledMouseDeviceInfo = - generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID); InputDeviceInfo anotherEnabledMouseDeviceInfo = - generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE); + generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, @@ -1177,7 +1194,7 @@ TEST_F(PointerChoreographerTest, WhenTouchpadIsAddedCreatesPointerController) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerCreated(ControllerType::MOUSE); } @@ -1185,7 +1202,7 @@ TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedRemovesPointerController) mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); // Remove the touchpad. @@ -1227,7 +1244,8 @@ TEST_F(PointerChoreographerTest, SetsDefaultTouchpadViewportForPointerController // the PointerController. mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertViewportSet(DISPLAY_ID); } @@ -1240,7 +1258,7 @@ TEST_F(PointerChoreographerTest, mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); firstDisplayPc->assertViewportSet(DISPLAY_ID); @@ -1258,7 +1276,7 @@ TEST_F(PointerChoreographerTest, TouchpadCallsNotifyPointerDisplayIdChanged) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); @@ -1269,7 +1287,7 @@ TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterTouchpadCallsNotifyPointe mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotNotified(); @@ -1283,12 +1301,12 @@ TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedCallsNotifyPointerDisplayI mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}}); - assertPointerDisplayIdNotified(ui::ADISPLAY_ID_NONE); + assertPointerDisplayIdNotified(ui::LogicalDisplayId::INVALID); assertPointerControllerRemoved(pc); } @@ -1302,12 +1320,12 @@ TEST_F(PointerChoreographerTest, mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE); assertPointerDisplayIdNotified(DISPLAY_ID); - // Set another viewport as a default mouse display ID. ui::ADISPLAY_ID_NONE will be notified - // before a touchpad event. + // Set another viewport as a default mouse display ID. ui::LogicalDisplayId::INVALID will be + // notified before a touchpad event. mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID); assertPointerControllerRemoved(firstDisplayPc); @@ -1321,7 +1339,7 @@ TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -1333,7 +1351,7 @@ TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) { MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(TOUCHPAD_POINTER) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); // Check that the PointerController updated the position and the pointer is shown. @@ -1351,7 +1369,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -1365,7 +1383,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), @@ -1379,7 +1397,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | @@ -1396,7 +1414,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN | @@ -1411,7 +1429,7 @@ TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) { .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10)) .classification(MotionClassification::MULTI_FINGER_SWIPE) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); mTestListener.assertNotifyMotionWasCalled( AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), @@ -1430,7 +1448,7 @@ TEST_F(PointerChoreographerTest, mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE), + ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, ANOTHER_DISPLAY_ID)}}); auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE); @@ -1469,7 +1487,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); @@ -1486,7 +1504,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD) .pointer(FIRST_TOUCH_POINTER) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); // Check that there's no update on the PointerController. @@ -1495,7 +1513,7 @@ TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) { // Check x-y coordinates, displayId and cursor position are not changed. mTestListener.assertNotifyMotionWasCalled( - AllOf(WithCoords(100, 200), WithDisplayId(ui::ADISPLAY_ID_NONE), + AllOf(WithCoords(100, 200), WithDisplayId(ui::LogicalDisplayId::INVALID), WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION))); } @@ -1506,7 +1524,7 @@ TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledTouchpadHidesPointer) mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, pc->getDisplayId()); ASSERT_TRUE(pc->isPointerShown()); @@ -1525,7 +1543,8 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouse) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertPointerIconNotSet(); @@ -1540,7 +1559,8 @@ TEST_F(PointerChoreographerTest, DoesNotSetMousePointerIconForWrongDisplayId) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertPointerIconNotSet(); @@ -1556,7 +1576,8 @@ TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForWrongDeviceId) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertPointerIconNotSet(); @@ -1572,7 +1593,8 @@ TEST_F(PointerChoreographerTest, SetsCustomPointerIconForMouse) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); pc->assertCustomPointerIconNotSet(); @@ -1595,7 +1617,7 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId()); @@ -1790,14 +1812,14 @@ TEST_P(StylusTestFixture, SetsPointerIconForMouseAndStylus) { mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}}); mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); mChoreographer.notifyMotion( MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE) .pointer(MOUSE_POINTER) .deviceId(DEVICE_ID) - .displayId(ui::ADISPLAY_ID_NONE) + .displayId(ui::LogicalDisplayId::INVALID) .build()); auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE); mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source) @@ -1825,7 +1847,7 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerOnDisplay) mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId()); @@ -1882,7 +1904,8 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerWhenDeviceC mChoreographer.setPointerIconVisibility(DISPLAY_ID, false); mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, + ui::LogicalDisplayId::INVALID)}}); auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, mousePc->getDisplayId()); @@ -1900,7 +1923,7 @@ TEST_F(PointerChoreographerTest, SetPointerIconVisibilityHidesPointerForTouchpad mChoreographer.notifyInputDevicesChanged( {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto touchpadPc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_EQ(DISPLAY_ID, touchpadPc->getDisplayId()); @@ -1945,7 +1968,8 @@ TEST_F(PointerChoreographerTest, DrawingTabletCanReportMouseEvent) { mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::LogicalDisplayId::INVALID)}}); // There should be no controller created when a drawing tablet is connected assertPointerControllerNotCreated(); @@ -1972,7 +1996,8 @@ TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { // First drawing tablet is added mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerNotCreated(); mChoreographer.notifyMotion( @@ -1987,9 +2012,10 @@ TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { // Second drawing tablet is added mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerNotRemoved(pc); mChoreographer.notifyMotion( @@ -2002,7 +2028,8 @@ TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) { // First drawing tablet is removed mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::LogicalDisplayId::INVALID)}}); assertPointerControllerNotRemoved(pc); // Second drawing tablet is removed @@ -2017,9 +2044,10 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { // Mouse and drawing tablet connected mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE), + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::LogicalDisplayId::INVALID), generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, - ui::ADISPLAY_ID_NONE)}}); + ui::LogicalDisplayId::INVALID)}}); auto pc = assertPointerControllerCreated(ControllerType::MOUSE); ASSERT_TRUE(pc->isPointerShown()); @@ -2034,7 +2062,8 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { // Remove the mouse device mChoreographer.notifyInputDevicesChanged( {/*id=*/0, - {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, ui::ADISPLAY_ID_NONE)}}); + {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE, + ui::LogicalDisplayId::INVALID)}}); // The mouse controller should not be removed, because the drawing tablet has produced a // mouse event, so we are treating it as a mouse too. diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp index c273e92de6..a36d526913 100644 --- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp +++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp @@ -64,7 +64,7 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, // Define a valid motion event. NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, - /*displayId=*/ui::ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER, action, + ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, @@ -439,7 +439,7 @@ TEST_F(PreferStylusOverTouchTest, MixedStylusAndTouchDeviceIsCanceledAtFirst) { InputDeviceInfo stylusDevice; stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, /*identifier=*/{}, "stylus device", /*external=*/false, - /*hasMic=*/false, ui::ADISPLAY_ID_NONE); + /*hasMic=*/false, ui::LogicalDisplayId::INVALID); notifyInputDevicesChanged({stylusDevice}); // The touchscreen device was removed, so we no longer remember anything about it. We should // again start blocking touch events from it. diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index 245497ca4c..2b62dd13ef 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -37,7 +37,7 @@ constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE; constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE; constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER; constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT; -constexpr ui::LogicalDisplayId DISPLAY_ID = ui::ADISPLAY_ID_DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; constexpr int32_t DISPLAY_WIDTH = 480; constexpr int32_t DISPLAY_HEIGHT = 800; constexpr std::optional NO_PORT = std::nullopt; // no physical port is specified diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp index 80430d1d22..853f628a13 100644 --- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp +++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp @@ -88,7 +88,7 @@ static NotifyMotionArgs generateMotionArgs(nsecs_t downTime, nsecs_t eventTime, // Define a valid motion event. NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, - /*displayId=*/ui::ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER, action, + ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0, /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties, @@ -104,7 +104,7 @@ static InputDeviceInfo generateTestDeviceInfo() { auto info = InputDeviceInfo(); info.initialize(DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias", - /*isExternal=*/false, /*hasMic=*/false, ui::ADISPLAY_ID_NONE); + /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID); info.addSource(AINPUT_SOURCE_TOUCHSCREEN); info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat=*/0, /*fuzz=*/0, X_RESOLUTION); @@ -434,7 +434,7 @@ TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListene TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) { // Create a basic key event and send to blocker NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3, - AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_DEFAULT, /*policyFlags=*/0, + AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/0, AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5, AMETA_NONE, /*downTime=*/6); diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp index 0446d76218..0b4ac1fe86 100644 --- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp @@ -54,7 +54,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral(), eventTime, readTime, /*deviceId=*/fdp.ConsumeIntegral(), - AINPUT_SOURCE_KEYBOARD, ui::ADISPLAY_ID_DEFAULT, + AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/fdp.ConsumeIntegral(), AKEY_EVENT_ACTION_DOWN, /*flags=*/fdp.ConsumeIntegral(), AKEYCODE_HOME, -- GitLab From 50c151ad3a741362d193f0c881e1232b79158d02 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Tue, 14 May 2024 06:11:12 +0000 Subject: [PATCH 332/465] Store layer snapshot in LayerRenderArea Accessing the layer snapshot from a RenderArea lessens the dependency on getting the layer snapshot from SF's mLayerSnapshotBuilder, which needs to run on the main thread. Bug: b/294936197 Test: atest SurfaceFlinger_test Change-Id: I7c2a77a432ddae3f7bdd39311a3a505aa8f763d6 --- services/surfaceflinger/LayerRenderArea.cpp | 9 +++++---- services/surfaceflinger/LayerRenderArea.h | 11 +++++++---- services/surfaceflinger/RenderArea.h | 6 ++++++ services/surfaceflinger/RenderAreaBuilder.h | 18 +++++++++++------- services/surfaceflinger/SurfaceFlinger.cpp | 7 +++---- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index c25ddb6286..f323ce7284 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -25,13 +25,14 @@ namespace android { -LayerRenderArea::LayerRenderArea(sp layer, const Rect& crop, ui::Size reqSize, - ui::Dataspace reqDataSpace, bool allowSecureLayers, - const ui::Transform& layerTransform, const Rect& layerBufferSize, - bool hintForSeamlessTransition) +LayerRenderArea::LayerRenderArea(sp layer, frontend::LayerSnapshot layerSnapshot, + const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, + bool allowSecureLayers, const ui::Transform& layerTransform, + const Rect& layerBufferSize, bool hintForSeamlessTransition) : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition, allowSecureLayers), mLayer(std::move(layer)), + mLayerSnapshot(std::move(layerSnapshot)), mLayerBufferSize(layerBufferSize), mCrop(crop), mTransform(layerTransform) {} diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h index b12afe8751..a12bfca54a 100644 --- a/services/surfaceflinger/LayerRenderArea.h +++ b/services/surfaceflinger/LayerRenderArea.h @@ -32,19 +32,22 @@ class SurfaceFlinger; class LayerRenderArea : public RenderArea { public: - LayerRenderArea(sp layer, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool allowSecureLayers, const ui::Transform& layerTransform, - const Rect& layerBufferSize, bool hintForSeamlessTransition); + LayerRenderArea(sp layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop, + ui::Size reqSize, ui::Dataspace reqDataSpace, bool allowSecureLayers, + const ui::Transform& layerTransform, const Rect& layerBufferSize, + bool hintForSeamlessTransition); const ui::Transform& getTransform() const override; bool isSecure() const override; sp getDisplayDevice() const override; Rect getSourceCrop() const override; - virtual sp getParentLayer() const { return mLayer; } + sp getParentLayer() const override { return mLayer; } + const frontend::LayerSnapshot* getLayerSnapshot() const override { return &mLayerSnapshot; } private: const sp mLayer; + const frontend::LayerSnapshot mLayerSnapshot; const Rect mLayerBufferSize; const Rect mCrop; diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h index 18a530473e..e8d20af4ad 100644 --- a/services/surfaceflinger/RenderArea.h +++ b/services/surfaceflinger/RenderArea.h @@ -4,6 +4,8 @@ #include #include + +#include "FrontEnd/LayerSnapshot.h" #include "Layer.h" namespace android { @@ -82,6 +84,10 @@ public: // capture operation. virtual sp getParentLayer() const { return nullptr; } + // If this is a LayerRenderArea, return the layer snapshot + // of the root layer of the capture operation + virtual const frontend::LayerSnapshot* getLayerSnapshot() const { return nullptr; } + // Returns whether the render result may be used for system animations that // must preserve the exact colors of the display. bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; } diff --git a/services/surfaceflinger/RenderAreaBuilder.h b/services/surfaceflinger/RenderAreaBuilder.h index 012acd2a67..a25c6e0b67 100644 --- a/services/surfaceflinger/RenderAreaBuilder.h +++ b/services/surfaceflinger/RenderAreaBuilder.h @@ -83,9 +83,12 @@ struct LayerRenderAreaBuilder : RenderAreaBuilder { layer(layer), childrenOnly(childrenOnly) {} - // Layer that the render area will be on + // Root layer of the render area sp layer; + // Layer snapshot of the root layer + frontend::LayerSnapshot layerSnapshot; + // Transform to be applied on the layers to transform them // into the logical render area ui::Transform layerTransform{ui::Transform()}; @@ -97,17 +100,18 @@ struct LayerRenderAreaBuilder : RenderAreaBuilder { bool childrenOnly; // Uses parent snapshot to determine layer transform and buffer size - void setLayerInfo(const frontend::LayerSnapshot* parentSnapshot) { + void setLayerSnapshot(const frontend::LayerSnapshot& parentSnapshot) { + layerSnapshot = parentSnapshot; if (!childrenOnly) { - layerTransform = parentSnapshot->localTransform.inverse(); + layerTransform = parentSnapshot.localTransform.inverse(); } - layerBufferSize = parentSnapshot->bufferSize; + layerBufferSize = parentSnapshot.bufferSize; } std::unique_ptr build() const override { - return std::make_unique(layer, crop, reqSize, reqDataSpace, - allowSecureLayers, layerTransform, layerBufferSize, - hintForSeamlessTransition); + return std::make_unique(layer, std::move(layerSnapshot), crop, reqSize, + reqDataSpace, allowSecureLayers, layerTransform, + layerBufferSize, hintForSeamlessTransition); } }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5181fb8b56..b3ddb57472 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8207,7 +8207,7 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( ALOGW("Couldn't find layer snapshot for %d", layerRenderAreaBuilder->layer->getSequence()); } else { - layerRenderAreaBuilder->setLayerInfo(snapshot); + layerRenderAreaBuilder->setLayerSnapshot(*snapshot); } } @@ -8302,9 +8302,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( Mutex::Autolock lock(mStateLock); const DisplayDevice* display = nullptr; if (parent) { - const frontend::LayerSnapshot* snapshot = mLayerLifecycleManagerEnabled - ? mLayerSnapshotBuilder.getSnapshot(parent->sequence) - : parent->getLayerSnapshot(); + const frontend::LayerSnapshot* snapshot = + mLayerSnapshotBuilder.getSnapshot(parent->sequence); if (snapshot) { display = findDisplay([layerStack = snapshot->outputFilter.layerStack]( const auto& display) { -- GitLab From 134c04db7eb04de2d37db16399e204b56fc64393 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 14 May 2024 11:12:25 -0700 Subject: [PATCH 333/465] Remove ADISPLAY_ID_ definitions These were replaced by LogicalDisplayId:: equivalents. Bug: 339106983 Test: presubmit Change-Id: Id4ed040c8e3a0d7fdfebbedfb1a7cafba592235d --- libs/ui/include/ui/LogicalDisplayId.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libs/ui/include/ui/LogicalDisplayId.h b/libs/ui/include/ui/LogicalDisplayId.h index d745758f0c..fd84b12c22 100644 --- a/libs/ui/include/ui/LogicalDisplayId.h +++ b/libs/ui/include/ui/LogicalDisplayId.h @@ -43,13 +43,6 @@ struct LogicalDisplayId : ftl::Constructible, constexpr inline LogicalDisplayId LogicalDisplayId::INVALID{-1}; constexpr inline LogicalDisplayId LogicalDisplayId::DEFAULT{0}; -/** - * Deprecated! Use LogicalDisplayId::INVALID / LogicalDisplayId::DEFAULT instead. - * TODO(b/339106983): remove these. - */ -[[deprecated]] constexpr LogicalDisplayId ADISPLAY_ID_NONE{-1}; -[[deprecated]] constexpr LogicalDisplayId ADISPLAY_ID_DEFAULT{0}; - inline std::ostream& operator<<(std::ostream& stream, LogicalDisplayId displayId) { return stream << displayId.val(); } -- GitLab From 018faea11a94d96ebc9e7294fc68f7df165c4875 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 8 May 2024 21:52:54 +0000 Subject: [PATCH 334/465] InputReader: Add API to get the last used input device This will be used by the InputMethodManagerService to notify the IME of the last input device type that was used to interact with Android when the IME is shown. This API eliminates the need for IMMS to add another global spy window to solve this problem. Bug: 336615195 Test: atest inputflinger_tests Change-Id: If594bd07bfd0a3cb542fc300854f1dd5717aeab2 --- .../inputflinger/include/InputReaderBase.h | 6 ++ services/inputflinger/reader/InputReader.cpp | 40 +++++++++- .../inputflinger/reader/include/InputReader.h | 5 ++ .../inputflinger/tests/InputReader_test.cpp | 77 +++++++++++++++++++ .../tests/fuzzers/InputReaderFuzzer.cpp | 2 + 5 files changed, 127 insertions(+), 3 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index e5c3aa08d1..62149c5a70 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -394,6 +394,12 @@ public: /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */ virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0; + + /* Get the ID of the InputDevice that was used most recently. + * + * Returns ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID if no device has been used since boot. + */ + virtual DeviceId getLastUsedInputDeviceId() = 0; }; // --- TouchAffineTransformation --- diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 12f52b899c..69555f8961 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -38,6 +38,8 @@ using android::base::StringPrintf; namespace android { +namespace { + /** * Determines if the identifiers passed are a sub-devices. Sub-devices are physical devices * that expose multiple input device paths such a keyboard that also has a touchpad input. @@ -49,8 +51,8 @@ namespace android { * inputs versus the same device plugged into multiple ports. */ -static bool isSubDevice(const InputDeviceIdentifier& identifier1, - const InputDeviceIdentifier& identifier2) { +bool isSubDevice(const InputDeviceIdentifier& identifier1, + const InputDeviceIdentifier& identifier2) { return (identifier1.vendor == identifier2.vendor && identifier1.product == identifier2.product && identifier1.bus == identifier2.bus && identifier1.version == identifier2.version && @@ -58,7 +60,7 @@ static bool isSubDevice(const InputDeviceIdentifier& identifier1, identifier1.location == identifier2.location); } -static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) { +bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) { const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action); if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER && actionMasked != AMOTION_EVENT_ACTION_DOWN && @@ -69,6 +71,28 @@ static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) { return isStylusToolType(motionArgs.pointerProperties[actionIndex].toolType); } +bool isNewGestureStart(const NotifyMotionArgs& motion) { + return motion.action == AMOTION_EVENT_ACTION_DOWN || + motion.action == AMOTION_EVENT_ACTION_HOVER_ENTER; +} + +bool isNewGestureStart(const NotifyKeyArgs& key) { + return key.action == AKEY_EVENT_ACTION_DOWN; +} + +// Return the event's device ID if it marks the start of a new gesture. +std::optional getDeviceIdOfNewGesture(const NotifyArgs& args) { + if (const auto* motion = std::get_if(&args); motion != nullptr) { + return isNewGestureStart(*motion) ? std::make_optional(motion->deviceId) : std::nullopt; + } + if (const auto* key = std::get_if(&args); key != nullptr) { + return isNewGestureStart(*key) ? std::make_optional(key->deviceId) : std::nullopt; + } + return std::nullopt; +} + +} // namespace + // --- InputReader --- InputReader::InputReader(std::shared_ptr eventHub, @@ -162,6 +186,11 @@ void InputReader::loopOnce() { } std::swap(notifyArgs, mPendingArgs); + + // Keep track of the last used device + for (const NotifyArgs& args : notifyArgs) { + mLastUsedDeviceId = getDeviceIdOfNewGesture(args).value_or(mLastUsedDeviceId); + } } // release lock // Flush queued events out to the listener. @@ -883,6 +912,11 @@ void InputReader::sysfsNodeChanged(const std::string& sysfsNodePath) { mEventHub->sysfsNodeChanged(sysfsNodePath); } +DeviceId InputReader::getLastUsedInputDeviceId() { + std::scoped_lock _l(mLock); + return mLastUsedDeviceId; +} + void InputReader::dump(std::string& dump) { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index d9ac917031..92a778a5bb 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -118,6 +118,8 @@ public: void sysfsNodeChanged(const std::string& sysfsNodePath) override; + DeviceId getLastUsedInputDeviceId() override; + protected: // These members are protected so they can be instrumented by test cases. virtual std::shared_ptr createDeviceLocked(nsecs_t when, int32_t deviceId, @@ -200,6 +202,9 @@ private: // records timestamp of the last key press on the physical keyboard nsecs_t mLastKeyDownTimestamp GUARDED_BY(mLock){0}; + // The input device that produced a new gesture most recently. + DeviceId mLastUsedDeviceId GUARDED_BY(mLock){ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID}; + // low-level input event decoding and device management [[nodiscard]] std::list processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index fcc52a8f31..e26cee4a53 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1184,6 +1185,82 @@ TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) { mFakeListener->assertNotifyCaptureWasNotCalled(); } +TEST_F(InputReaderTest, GetLastUsedInputDeviceId) { + constexpr int32_t FIRST_DEVICE_ID = END_RESERVED_ID + 1000; + constexpr int32_t SECOND_DEVICE_ID = FIRST_DEVICE_ID + 1; + FakeInputMapper& firstMapper = + addDeviceWithFakeInputMapper(FIRST_DEVICE_ID, FIRST_DEVICE_ID, "first", + InputDeviceClass::KEYBOARD, AINPUT_SOURCE_KEYBOARD, + /*configuration=*/nullptr); + FakeInputMapper& secondMapper = + addDeviceWithFakeInputMapper(SECOND_DEVICE_ID, SECOND_DEVICE_ID, "second", + InputDeviceClass::TOUCH_MT, AINPUT_SOURCE_STYLUS, + /*configuration=*/nullptr); + + ASSERT_EQ(ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID, mReader->getLastUsedInputDeviceId()); + + // Start a new key gesture from the first device + firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .deviceId(FIRST_DEVICE_ID) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(firstMapper.getDeviceId(), mReader->getLastUsedInputDeviceId()); + + // Start a new touch gesture from the second device + secondMapper.setProcessResult( + {MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER)) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId()); + + // Releasing the key is not a new gesture, so it does not update the last used device + firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD) + .deviceId(FIRST_DEVICE_ID) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId()); + + // But pressing a new key does start a new gesture + firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .deviceId(FIRST_DEVICE_ID) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId()); + + // Moving or ending a touch gesture does not update the last used device + secondMapper.setProcessResult( + {MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS)) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId()); + secondMapper.setProcessResult({MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS)) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId()); + + // Starting a new hover gesture updates the last used device + secondMapper.setProcessResult( + {MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS) + .deviceId(SECOND_DEVICE_ID) + .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS)) + .build()}); + mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0); + mReader->loopOnce(); + ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId()); +} + class FakeVibratorInputMapper : public FakeInputMapper { public: FakeVibratorInputMapper(InputDeviceContext& deviceContext, diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index 34ea54ca4b..a19726a479 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -169,6 +169,8 @@ public: reader->sysfsNodeChanged(sysfsNodePath); } + DeviceId getLastUsedInputDeviceId() override { return reader->getLastUsedInputDeviceId(); } + private: std::unique_ptr reader; }; -- GitLab From a7873cc66b57db38e688d9d4e95883683110846f Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 11 Apr 2024 16:33:28 -0400 Subject: [PATCH 335/465] FTL: Touch up Concat - Silence warning about signedness change. - Disable copying to prevent stray end_ pointer. Bug: 185536303 Test: Build Change-Id: Ief31910bd51e9582a576ace554a5f7bafe46bf02 --- include/ftl/concat.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/ftl/concat.h b/include/ftl/concat.h index e0774d39f3..7680bed5e8 100644 --- a/include/ftl/concat.h +++ b/include/ftl/concat.h @@ -57,7 +57,7 @@ struct Concat : Concat::N, Ts...> { template struct Concat { static constexpr std::size_t max_size() { return N; } - constexpr std::size_t size() const { return end_ - buffer_; } + constexpr std::size_t size() const { return static_cast(end_ - buffer_); } constexpr const char* c_str() const { return buffer_; } @@ -68,6 +68,8 @@ struct Concat { protected: constexpr Concat() : end_(buffer_) {} + constexpr Concat(const Concat&) = delete; + constexpr void append() { *end_ = '\0'; } char buffer_[N + 1]; -- GitLab From 189d18256ddd9e38987b0a52363eca3d110aae29 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 3 May 2024 17:30:26 -0400 Subject: [PATCH 336/465] FTL: Add Optional::ok_or and FTL_TRY Optional::ok_or maps to Expected where nullopt becomes E. FTL_TRY unwraps T for Expected or does an early out on error. Bug: 185536303 Test: ftl_test Change-Id: Ia03f7e3d8773878db1c493b62772ab2c2b7a4fed --- include/ftl/expected.h | 48 ++++++++++++++++++++++++++++++++++++++ include/ftl/optional.h | 10 +++++++- include/ftl/unit.h | 18 ++++++++++++++ libs/ftl/Android.bp | 1 + libs/ftl/expected_test.cpp | 41 ++++++++++++++++++++++++++++++++ libs/ftl/optional_test.cpp | 15 ++++++++++++ 6 files changed, 132 insertions(+), 1 deletion(-) diff --git a/include/ftl/expected.h b/include/ftl/expected.h index 12b6102b6f..57448dc1e0 100644 --- a/include/ftl/expected.h +++ b/include/ftl/expected.h @@ -18,9 +18,57 @@ #include #include +#include #include +// Given an expression `expr` that evaluates to an ftl::Expected result (R for short), FTL_TRY +// unwraps T out of R, or bails out of the enclosing function F if R has an error E. The return type +// of F must be R, since FTL_TRY propagates R in the error case. As a special case, ftl::Unit may be +// used as the error E to allow FTL_TRY expressions when F returns `void`. +// +// The non-standard syntax requires `-Wno-gnu-statement-expression-from-macro-expansion` to compile. +// The UnitToVoid conversion allows the macro to be used for early exit from a function that returns +// `void`. +// +// Example usage: +// +// using StringExp = ftl::Expected; +// +// StringExp repeat(StringExp exp) { +// const std::string str = FTL_TRY(exp); +// return StringExp(str + str); +// } +// +// assert(StringExp("haha"s) == repeat(StringExp("ha"s))); +// assert(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) { +// return e == std::errc::bad_message; +// })); +// +// +// FTL_TRY may be used in void-returning functions by using ftl::Unit as the error type: +// +// void uppercase(char& c, ftl::Optional opt) { +// c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit()))); +// } +// +// char c = '?'; +// uppercase(c, std::nullopt); +// assert(c == '?'); +// +// uppercase(c, 'a'); +// assert(c == 'A'); +// +#define FTL_TRY(expr) \ + ({ \ + auto exp_ = (expr); \ + if (!exp_.has_value()) { \ + using E = decltype(exp_)::error_type; \ + return android::ftl::details::UnitToVoid::from(std::move(exp_)); \ + } \ + exp_.value(); \ + }) + namespace android::ftl { // Superset of base::expected with monadic operations. diff --git a/include/ftl/optional.h b/include/ftl/optional.h index 94d8e3d7cb..e245d88c1c 100644 --- a/include/ftl/optional.h +++ b/include/ftl/optional.h @@ -20,13 +20,14 @@ #include #include +#include #include namespace android::ftl { // Superset of std::optional with monadic operations, as proposed in https://wg21.link/P0798R8. // -// TODO: Remove in C++23. +// TODO: Remove standard APIs in C++23. // template struct Optional final : std::optional { @@ -109,6 +110,13 @@ struct Optional final : std::optional { return std::forward(f)(); } + // Maps this Optional to expected where nullopt becomes E. + template + constexpr auto ok_or(E&& e) && -> base::expected { + if (has_value()) return std::move(value()); + return base::unexpected(std::forward(e)); + } + // Delete new for this class. Its base doesn't have a virtual destructor, and // if it got deleted via base class pointer, it would cause undefined // behavior. There's not a good reason to allocate this object on the heap diff --git a/include/ftl/unit.h b/include/ftl/unit.h index e38230b976..62549a3f9d 100644 --- a/include/ftl/unit.h +++ b/include/ftl/unit.h @@ -58,4 +58,22 @@ constexpr auto unit_fn(F&& f) -> UnitFn> { return {std::forward(f)}; } +namespace details { + +// Identity function for all T except Unit, which maps to void. +template +struct UnitToVoid { + template + static auto from(U&& value) { + return value; + } +}; + +template <> +struct UnitToVoid { + template + static void from(U&&) {} +}; + +} // namespace details } // namespace android::ftl diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index 4b41152c12..368f5e079c 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -41,5 +41,6 @@ cc_test { "-Wextra", "-Wpedantic", "-Wthread-safety", + "-Wno-gnu-statement-expression-from-macro-expansion", ], } diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp index 8cb07e4696..9b7f017df3 100644 --- a/libs/ftl/expected_test.cpp +++ b/libs/ftl/expected_test.cpp @@ -15,8 +15,11 @@ */ #include +#include +#include #include +#include #include #include @@ -74,4 +77,42 @@ TEST(Expected, ValueOpt) { } } +namespace { + +IntExp increment(IntExp exp) { + const int i = FTL_TRY(exp); + return IntExp(i + 1); +} + +StringExp repeat(StringExp exp) { + const std::string str = FTL_TRY(exp); + return StringExp(str + str); +} + +void uppercase(char& c, ftl::Optional opt) { + c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit()))); +} + +} // namespace + +// Keep in sync with example usage in header file. +TEST(Expected, Try) { + EXPECT_EQ(IntExp(100), increment(IntExp(99))); + EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) { + return e == std::errc::value_too_large; + })); + + EXPECT_EQ(StringExp("haha"s), repeat(StringExp("ha"s))); + EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) { + return e == std::errc::bad_message; + })); + + char c = '?'; + uppercase(c, std::nullopt); + EXPECT_EQ(c, '?'); + + uppercase(c, 'a'); + EXPECT_EQ(c, 'A'); +} + } // namespace android::test diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp index 91bf7bc5b6..e7f1f14ad9 100644 --- a/libs/ftl/optional_test.cpp +++ b/libs/ftl/optional_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include using namespace std::placeholders; @@ -204,6 +206,19 @@ TEST(Optional, OrElse) { .or_else([] { return Optional(-1); })); } +TEST(Optional, OkOr) { + using CharExp = ftl::Expected; + using StringExp = ftl::Expected; + + EXPECT_EQ(CharExp('z'), Optional('z').ok_or(std::errc::broken_pipe)); + EXPECT_EQ(CharExp(ftl::Unexpected(std::errc::broken_pipe)), + Optional().ok_or(std::errc::broken_pipe)); + + EXPECT_EQ(StringExp("abc"s), Optional("abc"s).ok_or(std::errc::protocol_error)); + EXPECT_EQ(StringExp(ftl::Unexpected(std::errc::protocol_error)), + Optional().ok_or(std::errc::protocol_error)); +} + // Comparison. namespace { -- GitLab From 9325608db31f708c363254fa8dff863a96ab1467 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 9 May 2024 17:04:34 -0700 Subject: [PATCH 337/465] SF: improve activeMode log in dumpsys Test: adb shell dumpsys SurfaceFlinger Change-Id: I06a03d3ce841ac18aad70c2945bdb75885fb8f50 --- services/surfaceflinger/Scheduler/RefreshRateSelector.cpp | 3 ++- .../Scheduler/include/scheduler/FrameRateMode.h | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index a37fb96b5b..2c664928ee 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -1590,7 +1590,8 @@ void RefreshRateSelector::dump(utils::Dumper& dumper) const { std::lock_guard lock(mLock); const auto activeMode = getActiveModeLocked(); - dumper.dump("activeMode"sv, to_string(activeMode)); + dumper.dump("renderRate"sv, to_string(activeMode.fps)); + dumper.dump("activeMode"sv, to_string(*activeMode.modePtr)); dumper.dump("displayModes"sv); { diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h index 59a6df23fd..f2be316452 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h @@ -36,8 +36,11 @@ struct FrameRateMode { }; inline std::string to_string(const FrameRateMode& mode) { - return to_string(mode.fps) + " (" + to_string(mode.modePtr->getPeakFps()) + "(" + - to_string(mode.modePtr->getVsyncRate()) + "))"; + return base::StringPrintf("{fps=%s, modePtr={id=%d, vsyncRate=%s, peakRefreshRate=%s}}", + to_string(mode.fps).c_str(), + ftl::to_underlying(mode.modePtr->getId()), + to_string(mode.modePtr->getVsyncRate()).c_str(), + to_string(mode.modePtr->getPeakFps()).c_str()); } } // namespace android::scheduler -- GitLab From e9dcf79f12b0d179c6e96a6de5920502f75c792e Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 14 May 2024 15:24:54 -0700 Subject: [PATCH 338/465] SF: do not skip a frame if it will not cause backpressure on dvrr Bug: 340633280 Test: android.platform.test.scenario.sysui.people.PeopleSpaceActivityMicrobenchmark#startPeopleSpaceActivity Change-Id: If230ef0ab2f8ac3846e4e3701e51d1821192548c --- .../Scheduler/VSyncPredictor.cpp | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 0b47924e36..85ce713703 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -650,29 +650,33 @@ std::optional VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTime const auto threshold = model.slope / 2; const auto lastFrameMissed = lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold; - if (FlagManager::getInstance().vrr_config() && lastFrameMissed) { - // If the last frame missed is the last vsync, we already shifted the timeline. Depends on - // whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a different - // fixup. There is no need to to shift the vsync timeline again. - vsyncTime += missedVsync.fixup.ns(); - ATRACE_FORMAT_INSTANT("lastFrameMissed"); - } else if (FlagManager::getInstance().vrr_config() && minFramePeriodOpt && mRenderRateOpt && - lastVsyncOpt) { - // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it - // first before trying to use it. - lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); - const auto vsyncDiff = vsyncTime - *lastVsyncOpt; - if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) { - // avoid a duplicate vsync - ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f which " - "is %.2f " - "from " - "prev. " - "adjust by %.2f", - static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, - static_cast(vsyncDiff) / 1e6f, - static_cast(mRenderRateOpt->getPeriodNsecs()) / 1e6f); - vsyncTime += mRenderRateOpt->getPeriodNsecs(); + const auto mightBackpressure = minFramePeriodOpt && mRenderRateOpt && + mRenderRateOpt->getPeriod() < 2 * (*minFramePeriodOpt); + if (FlagManager::getInstance().vrr_config()) { + if (lastFrameMissed) { + // If the last frame missed is the last vsync, we already shifted the timeline. Depends + // on whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a + // different fixup. There is no need to to shift the vsync timeline again. + vsyncTime += missedVsync.fixup.ns(); + ATRACE_FORMAT_INSTANT("lastFrameMissed"); + } else if (mightBackpressure && lastVsyncOpt) { + // lastVsyncOpt is based on the old timeline before we shifted it. we should correct it + // first before trying to use it. + lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt); + const auto vsyncDiff = vsyncTime - *lastVsyncOpt; + if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) { + // avoid a duplicate vsync + ATRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f " + "which " + "is %.2f " + "from " + "prev. " + "adjust by %.2f", + static_cast(vsyncTime - TimePoint::now().ns()) / 1e6f, + static_cast(vsyncDiff) / 1e6f, + static_cast(mRenderRateOpt->getPeriodNsecs()) / 1e6f); + vsyncTime += mRenderRateOpt->getPeriodNsecs(); + } } } -- GitLab From 7b48c207960226c57c5be154f7f84be73c4bd4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Wagner?= Date: Mon, 13 May 2024 19:44:08 +0000 Subject: [PATCH 339/465] Filter hook entry points by ICD entry point presence For device proc hooks which intercept core functions check whether there exists an exposed core function from the ICD, and skip exposure if none is found. This avoids having to replicate exposure filtering based on requested Vulkan API versions inside the Loader - rely on the ICD to handle it correctly. Bug: 309752984 Change-Id: Ibeb13dae8eccaa859072ee5233013d99d5b26ef0 --- vulkan/libvulkan/driver.cpp | 8 +++++++- vulkan/scripts/driver_generator.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp index 7ea98f5469..3f89960e32 100644 --- a/vulkan/libvulkan/driver.cpp +++ b/vulkan/libvulkan/driver.cpp @@ -964,14 +964,20 @@ PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* pName) { PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* pName) { const ProcHook* hook = GetProcHook(pName); + PFN_vkVoidFunction drv_func = GetData(device).driver.GetDeviceProcAddr(device, pName); + if (!hook) - return GetData(device).driver.GetDeviceProcAddr(device, pName); + return drv_func; if (hook->type != ProcHook::DEVICE) { ALOGE("internal vkGetDeviceProcAddr called for %s", pName); return nullptr; } + // Don't hook if we don't have a device entry function below for the core function. + if (!drv_func && (hook->extension >= ProcHook::EXTENSION_CORE_1_0)) + return nullptr; + return (GetData(device).hook_extensions[hook->extension]) ? hook->proc : nullptr; } diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py index 78b550c202..48c0ae9304 100644 --- a/vulkan/scripts/driver_generator.py +++ b/vulkan/scripts/driver_generator.py @@ -239,6 +239,8 @@ struct ProcHook { f.write(gencom.indent(2) + gencom.base_ext_name(ext) + ',\n') f.write('\n') + # EXTENSION_CORE_xxx API list must be the last set of enums after the extensions. + # This allows to easily identify "a" core function hook for version in gencom.version_code_list: f.write(gencom.indent(2) + 'EXTENSION_CORE_' + version + ',\n') -- GitLab From ba3344cc1c8ea990314686efbd108f336854d831 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 14 May 2024 16:54:28 +0000 Subject: [PATCH 340/465] InputEvent_test: fix parameter comment style It seems I missed these in change Id5207b5a621c9a1ac0a46a8b2ff2bbf9aa43e726. Bug: 245989146 Test: m inputflinger_tests Flag: EXEMPT refactor Change-Id: I23c2d4b33d8884868ba4b71cfe1e20ff76240089 --- libs/input/tests/InputEvent_test.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 704ea46fe9..476b5cf818 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -858,11 +858,10 @@ MotionEvent createMotionEvent(int32_t source, uint32_t action, float x, float y, pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy); nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC); MotionEvent event; - event.initialize(InputEvent::nextId(), /* deviceId */ 1, source, ui::LogicalDisplayId::DEFAULT, - INVALID_HMAC, action, - /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE, - /* buttonState */ 0, MotionClassification::NONE, transform, - /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION, + event.initialize(InputEvent::nextId(), /*deviceId=*/1, source, ui::LogicalDisplayId::DEFAULT, + INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0, + AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, transform, + /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, rawTransform, eventTime, eventTime, pointerCoords.size(), pointerProperties.data(), pointerCoords.data()); return event; -- GitLab From 6aee343c422c68c2622870cc3de4b728ce351408 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 15 May 2024 10:43:43 +0000 Subject: [PATCH 341/465] TouchpadInputMapper: remove usage atom log lines Change Ifc06bdee240471692e8b3c4fc9b67a99d10d203d fixed the flakiness that these logs were added to diagnose, so we don't need them anymore. This is a pure revert of changes I23346b4904f83abb6cc5a07b687bb0a9802c6bfa and Ic062a5fe5db905c4691147a1267600981b9017e8. Bug: 324047709 Test: m checkinput Test: atest 'android.input.cts.hostside.InputAtomsTest#testTouchpadUsageAtom_FingerAndPalmCounts' Flag: NONE trivial log removal / pure revert Change-Id: I3114f7444fa40efd8c4ad080e28bbf11f00d1534 --- services/inputflinger/reader/mapper/TouchpadInputMapper.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index e157862f85..713cfa22a9 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -183,7 +183,6 @@ private: static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* outEventList, void* cookie) { - ALOGI("Received pull request for touchpad usage atom"); LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE); MetricsAccumulator& accumulator = MetricsAccumulator::getInstance(); accumulator.produceAtomsAndReset(*outEventList); @@ -191,14 +190,12 @@ private: } void produceAtomsAndReset(AStatsEventList& outEventList) { - ALOGI("Acquiring lock for touchpad usage metrics..."); std::scoped_lock lock(mLock); produceAtomsLocked(outEventList); resetCountersLocked(); } void produceAtomsLocked(AStatsEventList& outEventList) const REQUIRES(mLock) { - ALOGI("Producing touchpad usage atoms for %zu counters", mCounters.size()); for (auto& [id, counters] : mCounters) { auto [busId, vendorId, productId, versionId] = id; addAStatsEvent(&outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId, -- GitLab From 13acbd2e715a2ae8433c920dc6c843e503153fc2 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 14 May 2024 22:36:40 +0000 Subject: [PATCH 342/465] GestureConverter: Reset fake finger on scroll start The following recent cleanup CL introduced a bug where the fake finger position was not being reset after a scroll gesture ends: I02bd7c8f4c4126c9ae7d2fdffd94175b37478925 Fix the newly introduced bug and add a test case. Fixes: 340417860 Test: atest inputflinger_tests Change-Id: I747ba4ace6d09424c10925cdd7a1f4b0ad6a6249 --- .../mapper/gestures/GestureConverter.cpp | 1 + .../tests/GestureConverter_test.cpp | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp index ff95857e96..e8e7376e92 100644 --- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp @@ -331,6 +331,7 @@ std::list GestureConverter::handleScroll(nsecs_t when, nsecs_t readT out += exitHover(when, readTime); mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE; + coords.clear(); coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); mDownTime = when; NotifyMotionArgs args = diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp index 1132e9284c..64223afbba 100644 --- a/services/inputflinger/tests/GestureConverter_test.cpp +++ b/services/inputflinger/tests/GestureConverter_test.cpp @@ -440,6 +440,43 @@ TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) { EXPECT_THAT(std::get(args.front()), WithGestureScrollDistance(0, 0, EPSILON)); } +TEST_F(GestureConverterTest, Scroll_ClearsFakeFingerPositionOnSubsequentScrollGestures) { + InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); + GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); + converter.setDisplayId(ui::LogicalDisplayId::DEFAULT); + + Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 15, -10); + std::list args = + converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture); + + Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -2, -5); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture); + + Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1, + GESTURES_FLING_START); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture); + Gesture flingGestureEnd(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 0, + GESTURES_FLING_TAP_DOWN); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGestureEnd); + + // Start a second scoll gesture, and ensure the fake finger is reset to (0, 0), instead of + // continuing from the position where the last scroll gesture's fake finger ended. + Gesture secondScrollStart(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 2, + 14); + args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, secondScrollStart); + ASSERT_THAT(args, + ElementsAre(VariantWith( + WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)), + VariantWith( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), + WithCoords(0, 0), + WithGestureScrollDistance(0, 0, EPSILON))), + VariantWith( + AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), + WithCoords(2, 14), + WithGestureScrollDistance(-2, -14, EPSILON))))); +} + TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) { InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID); GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID); -- GitLab From 0cd9e86cb5e57e19e4b099cb2f5298dbca3f1250 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Wed, 15 May 2024 18:37:02 -0700 Subject: [PATCH 343/465] Fix hdrsdrratiooverlay backdoor logic. - we only accept 1/0 for this backdoor. However, in the current logic, if other non-zero number is set, the current logic assumes this overlay to be enabled. But in the switch code block, we don't enable this backdoor and instead the logic falls into default section. Bug: n/a Test: test this backdoor Change-Id: Icfc7d6b661667cebaba103177559c56b05c0cc00 --- services/surfaceflinger/SurfaceFlinger.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 0d2e5142bc..ffa9af9493 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7499,14 +7499,11 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r auto future = mScheduler->schedule( [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) { n = data.readInt32(); - mHdrSdrRatioOverlay = n != 0; - switch (n) { - case 0: - case 1: - enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay); - break; - default: - reply->writeBool(isHdrSdrRatioOverlayEnabled()); + if (n == 0 || n == 1) { + mHdrSdrRatioOverlay = n != 0; + enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay); + } else { + reply->writeBool(isHdrSdrRatioOverlayEnabled()); } }); future.wait(); -- GitLab From 422b81cb4ffcf28bb5becfb1fcaf93008b41d618 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 16 May 2024 05:44:28 +0000 Subject: [PATCH 344/465] Update mirror layer tests to support detach mirror flag We changed mirroring behavior to ignore the local transforms to support cases like mirroring a freeform window. This cl fixes the tests to update the position of the mirrored layer if the flag is set. Bug: 337845753 Test: presubmit Change-Id: I28294c36afb4d9f0de2e6c6017332617315f0c4d --- .../FrontEnd/LayerSnapshotBuilder.cpp | 1 + .../surfaceflinger/tests/MirrorLayer_test.cpp | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index e40c79cf62..f2497d4bc8 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -362,6 +362,7 @@ LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { snapshot.gameMode = gui::GameMode::Unsupported; snapshot.frameRate = {}; snapshot.fixedTransformHint = ui::Transform::ROT_INVALID; + snapshot.ignoreLocalTransform = false; return snapshot; } diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp index 0ea0824732..d97d433160 100644 --- a/services/surfaceflinger/tests/MirrorLayer_test.cpp +++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp @@ -19,6 +19,7 @@ #pragma clang diagnostic ignored "-Wconversion" #include +#include #include #include "LayerTransactionTest.h" #include "utils/TransactionUtils.h" @@ -78,6 +79,10 @@ TEST_F(MirrorLayerTest, MirrorColorLayer) { .show(mirrorLayer) .apply(); + if (FlagManager::getInstance().detached_mirror()) { + Transaction().setPosition(mirrorLayer, 550, 550).apply(); + } + { SCOPED_TRACE("Initial Mirror"); auto shot = screenshot(); @@ -172,6 +177,9 @@ TEST_F(MirrorLayerTest, MirrorBufferLayer) { .show(mirrorLayer) .apply(); + if (FlagManager::getInstance().detached_mirror()) { + Transaction().setPosition(mirrorLayer, 550, 550).apply(); + } { SCOPED_TRACE("Initial Mirror BufferQueueLayer"); auto shot = screenshot(); @@ -263,6 +271,9 @@ TEST_F(MirrorLayerTest, InitialMirrorState) { .setLayer(mirrorLayer, INT32_MAX - 1) .apply(); + if (FlagManager::getInstance().detached_mirror()) { + Transaction().setPosition(mirrorLayer, 550, 550).apply(); + } { SCOPED_TRACE("Offscreen Mirror"); auto shot = screenshot(); @@ -313,8 +324,15 @@ TEST_F(MirrorLayerTest, OffscreenMirrorScreenshot) { ASSERT_NE(mirrorLayer, nullptr); } + sp mirrorParent = + createLayer("Grandchild layer", 50, 50, ISurfaceComposerClient::eFXSurfaceBufferState); + // Show the mirror layer, but don't reparent to a layer on screen. - Transaction().show(mirrorLayer).apply(); + Transaction().reparent(mirrorLayer, mirrorParent).show(mirrorLayer).apply(); + + if (FlagManager::getInstance().detached_mirror()) { + Transaction().setPosition(mirrorLayer, 50, 50).apply(); + } { SCOPED_TRACE("Offscreen Mirror"); @@ -331,7 +349,7 @@ TEST_F(MirrorLayerTest, OffscreenMirrorScreenshot) { SCOPED_TRACE("Capture Mirror"); // Capture just the mirror layer and child. LayerCaptureArgs captureArgs; - captureArgs.layerHandle = mirrorLayer->getHandle(); + captureArgs.layerHandle = mirrorParent->getHandle(); captureArgs.sourceCrop = childBounds; std::unique_ptr shot; ScreenCapture::captureLayers(&shot, captureArgs); -- GitLab From 14beed7625fbbe51e600ea672dd831c302f1585c Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 15 May 2024 17:16:45 -0700 Subject: [PATCH 345/465] SF: explicitly mark surface frame without a composite as non janky otherwise these surface frames waits in the current display frame for the next composite and they are marked as janky, even though they had no affect on the displayed frame. Bug: 340633280 Test: android.platform.test.scenario.sysui.people.PeopleSpaceActivityMicrobenchmark#startPeopleSpaceActivity Change-Id: I2774e88627e39bce18c04af4b20e2f6973121a42 --- .../FrameTimeline/FrameTimeline.cpp | 24 +++++++++ .../FrameTimeline/FrameTimeline.h | 9 ++++ .../Scheduler/ISchedulerCallback.h | 1 + .../surfaceflinger/Scheduler/Scheduler.cpp | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 6 +++ services/surfaceflinger/SurfaceFlinger.h | 2 + .../surfaceflinger/common/FlagManager.cpp | 2 + .../common/include/common/FlagManager.h | 1 + .../surfaceflinger_flags_new.aconfig | 11 ++++ .../tests/unittests/FrameTimelineTest.cpp | 51 +++++++++++++++++++ .../unittests/mock/MockSchedulerCallback.h | 2 + 11 files changed, 110 insertions(+) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index d0e2d7a451..8369a1ec64 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -681,6 +681,15 @@ void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType, } } +void SurfaceFrame::onCommitNotComposited(Fps refreshRate, Fps displayFrameRenderRate) { + std::scoped_lock lock(mMutex); + + mDisplayFrameRenderRate = displayFrameRenderRate; + mActuals.presentTime = mPredictions.presentTime; + nsecs_t deadlineDelta = 0; + classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, deadlineDelta); +} + void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const { int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing(); @@ -912,6 +921,15 @@ void FrameTimeline::setSfPresent(nsecs_t sfPresentTime, finalizeCurrentDisplayFrame(); } +void FrameTimeline::onCommitNotComposited() { + ATRACE_CALL(); + std::scoped_lock lock(mMutex); + mCurrentDisplayFrame->onCommitNotComposited(); + mCurrentDisplayFrame.reset(); + mCurrentDisplayFrame = std::make_shared(mTimeStats, mJankClassificationThresholds, + &mTraceCookieCounter); +} + void FrameTimeline::DisplayFrame::addSurfaceFrame(std::shared_ptr surfaceFrame) { mSurfaceFrames.push_back(surfaceFrame); } @@ -1094,6 +1112,12 @@ void FrameTimeline::DisplayFrame::onPresent(nsecs_t signalTime, nsecs_t previous } } +void FrameTimeline::DisplayFrame::onCommitNotComposited() { + for (auto& surfaceFrame : mSurfaceFrames) { + surfaceFrame->onCommitNotComposited(mRefreshRate, mRenderRate); + } +} + void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const { int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing(); diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h index a76f7d477a..21bc95a4c5 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h @@ -204,6 +204,8 @@ public: void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate, Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta); + // Sets the frame as none janky as there was no real display frame. + void onCommitNotComposited(Fps refreshRate, Fps displayFrameRenderRate); // All the timestamps are dumped relative to the baseTime void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const; // Dumps only the layer, token, is buffer, jank metadata, prediction and present states. @@ -318,6 +320,10 @@ public: virtual void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr& presentFence, const std::shared_ptr& gpuFence) = 0; + // Tells FrameTimeline that a frame was committed but not composited. This is used to flush + // all the associated surface frames. + virtual void onCommitNotComposited() = 0; + // Args: // -jank : Dumps only the Display Frames that are either janky themselves // or contain janky Surface Frames. @@ -390,6 +396,8 @@ public: std::optional predictions, nsecs_t wakeUpTime); // Sets the appropriate metadata and classifies the jank. void onPresent(nsecs_t signalTime, nsecs_t previousPresentTime); + // Flushes all the surface frames as those were not generating any actual display frames. + void onCommitNotComposited(); // Adds the provided SurfaceFrame to the current display frame. void addSurfaceFrame(std::shared_ptr surfaceFrame); @@ -475,6 +483,7 @@ public: void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override; void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr& presentFence, const std::shared_ptr& gpuFence = FenceTime::NO_FENCE) override; + void onCommitNotComposited() override; void parseArgs(const Vector& args, std::string& result) override; void setMaxDisplayFrames(uint32_t size) override; float computeFps(const std::unordered_set& layerIds) override; diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h index 9f4f5b6d39..43cdb5ec41 100644 --- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h @@ -32,6 +32,7 @@ struct ISchedulerCallback { virtual void onChoreographerAttached() = 0; virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull, Fps renderRate) = 0; + virtual void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) = 0; protected: ~ISchedulerCallback() = default; diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 005ec05aab..f72e7a05d5 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -221,6 +221,7 @@ void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId, if (FlagManager::getInstance().vrr_config()) { compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId); } + mSchedulerCallback.onCommitNotComposited(pacesetterPtr->displayId); return; } } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b3ddb57472..410c420353 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4405,6 +4405,12 @@ void SurfaceFlinger::sendNotifyExpectedPresentHint(PhysicalDisplayId displayId) scheduleNotifyExpectedPresentHint(displayId); } +void SurfaceFlinger::onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) { + if (FlagManager::getInstance().commit_not_composited()) { + mFrameTimeline->onCommitNotComposited(); + } +} + void SurfaceFlinger::initScheduler(const sp& display) { using namespace scheduler; diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 84745150d0..edcc8d3206 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -700,6 +700,8 @@ private: void onChoreographerAttached() override; void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull, Fps renderRate) override; + void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) override + REQUIRES(kMainThreadContext); // ICEPowerCallback overrides: void notifyCpuLoadUp() override; diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 45b3290b2b..e73cf5d785 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -144,6 +144,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(deprecate_vsync_sf); DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter); DUMP_READ_ONLY_FLAG(detached_mirror); + DUMP_READ_ONLY_FLAG(commit_not_composited); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -238,6 +239,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, ""); FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, ""); FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, ""); FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, ""); +FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 592e7745c9..327cc4ae41 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -83,6 +83,7 @@ public: bool deprecate_vsync_sf() const; bool allow_n_vsyncs_in_targeter() const; bool detached_mirror() const; + bool commit_not_composited() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 4d3195db7f..f1ec3e1ec2 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -21,6 +21,17 @@ flag { } } # ce_fence_promise + flag { + name: "commit_not_composited" + namespace: "core_graphics" + description: "mark frames as non janky if the transaction resulted in no composition" + bug: "340633280" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } + } # commit_not_composited + flag { name: "deprecate_vsync_sf" namespace: "core_graphics" diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index ddc3967c40..9fd687c6e7 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -341,6 +341,57 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_presentedFramesUpdated) { EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt); } +TEST_F(FrameTimelineTest, displayFrameSkippedComposition) { + // Layer specific increment + EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(1); + auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30}); + int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30}); + FrameTimelineInfo ftInfo; + ftInfo.vsyncId = surfaceFrameToken1; + ftInfo.inputEventId = sInputEventId; + auto surfaceFrame1 = + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); + auto surfaceFrame2 = + mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo, + sLayerNameTwo, sLayerNameTwo, + /*isBuffer*/ true, sGameMode); + + mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11); + surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); + mFrameTimeline->addSurfaceFrame(surfaceFrame1); + mFrameTimeline->onCommitNotComposited(); + + EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 30); + ASSERT_NE(surfaceFrame1->getJankType(), std::nullopt); + EXPECT_EQ(*surfaceFrame1->getJankType(), JankType::None); + ASSERT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt); + EXPECT_EQ(*surfaceFrame1->getJankSeverityType(), JankSeverityType::None); + + mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11); + surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented); + mFrameTimeline->addSurfaceFrame(surfaceFrame2); + mFrameTimeline->setSfPresent(26, presentFence1); + + auto displayFrame = getDisplayFrame(0); + auto& presentedSurfaceFrame2 = getSurfaceFrame(0, 0); + presentFence1->signalForTest(42); + + // Fences haven't been flushed yet, so it should be 0 + EXPECT_EQ(displayFrame->getActuals().presentTime, 0); + EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 0); + + addEmptyDisplayFrame(); + + // Fences have flushed, so the present timestamps should be updated + EXPECT_EQ(displayFrame->getActuals().presentTime, 42); + EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 42); + EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt); + EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt); +} + TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) { // Insert kMaxDisplayFrames' count of DisplayFrames to fill the deque int frameTimeFactor = 0; diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index 4ca05423d7..dec5fa56ea 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -30,6 +30,7 @@ struct SchedulerCallback final : ISchedulerCallback { MOCK_METHOD(void, onChoreographerAttached, (), (override)); MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull, Fps), (override)); + MOCK_METHOD(void, onCommitNotComposited, (PhysicalDisplayId), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { @@ -39,6 +40,7 @@ struct NoOpSchedulerCallback final : ISchedulerCallback { void triggerOnFrameRateOverridesChanged() override {} void onChoreographerAttached() override {} void onExpectedPresentTimePosted(TimePoint, ftl::NonNull, Fps) override {} + void onCommitNotComposited(PhysicalDisplayId) override {} }; } // namespace android::scheduler::mock -- GitLab From f1ad68a1a9fbdeb62999ccaee21643783101157c Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 23 Apr 2024 19:05:38 -0500 Subject: [PATCH 346/465] Fix transaction sanitization Bug: 336648041 Bug: 336648613 Test: CredentialsTest Change-Id: I53894d014bfabc9c958a6f533d7e3b3a6dcd0a34 (cherry picked from commit 04e41761914c3c3aaca965103be3679b7a7af76f) Merged-In: I53894d014bfabc9c958a6f533d7e3b3a6dcd0a34 --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- .../surfaceflinger/tests/Credentials_test.cpp | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index db205b8a95..d606788053 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4513,7 +4513,7 @@ status_t SurfaceFlinger::setTransactionState( const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid); - for (auto composerState : states) { + for (auto& composerState : states) { composerState.state.sanitize(permissions); } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 69e9a169e3..2d18166da5 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -401,8 +401,13 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { .apply(); } - // Called from non privileged process - Transaction().setTrustedOverlay(surfaceControl, true); + // Attempt to set a trusted overlay from a non-privileged process. This should fail silently. + { + UIDFaker f{AID_BIN}; + Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true); + } + + // Verify that the layer was not made a trusted overlay. { UIDFaker f(AID_SYSTEM); auto windowIsPresentAndNotTrusted = [&](const std::vector& windowInfos) { @@ -413,12 +418,14 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { } return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); }; - windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted); + ASSERT_TRUE( + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted)); } + // Verify that privileged processes are able to set trusted overlays. { UIDFaker f(AID_SYSTEM); - Transaction().setTrustedOverlay(surfaceControl, true); + Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true); auto windowIsPresentAndTrusted = [&](const std::vector& windowInfos) { auto foundWindowInfo = WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); @@ -427,7 +434,8 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { } return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); }; - windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted); + ASSERT_TRUE( + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted)); } } -- GitLab From 07abf83914f99ae46af52fd21bf84d0c36d59721 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 23 Apr 2024 19:05:38 -0500 Subject: [PATCH 347/465] Fix transaction sanitization Bug: 336648041 Bug: 336648613 Test: CredentialsTest Change-Id: I53894d014bfabc9c958a6f533d7e3b3a6dcd0a34 (cherry picked from commit 04e41761914c3c3aaca965103be3679b7a7af76f) Merged-In: I53894d014bfabc9c958a6f533d7e3b3a6dcd0a34 --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- .../surfaceflinger/tests/Credentials_test.cpp | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index e62c1181ed..5dbb39d855 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4509,7 +4509,7 @@ status_t SurfaceFlinger::setTransactionState( const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid); - for (auto composerState : states) { + for (auto& composerState : states) { composerState.state.sanitize(permissions); } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 69e9a169e3..2d18166da5 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -401,8 +401,13 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { .apply(); } - // Called from non privileged process - Transaction().setTrustedOverlay(surfaceControl, true); + // Attempt to set a trusted overlay from a non-privileged process. This should fail silently. + { + UIDFaker f{AID_BIN}; + Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true); + } + + // Verify that the layer was not made a trusted overlay. { UIDFaker f(AID_SYSTEM); auto windowIsPresentAndNotTrusted = [&](const std::vector& windowInfos) { @@ -413,12 +418,14 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { } return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); }; - windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted); + ASSERT_TRUE( + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted)); } + // Verify that privileged processes are able to set trusted overlays. { UIDFaker f(AID_SYSTEM); - Transaction().setTrustedOverlay(surfaceControl, true); + Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true); auto windowIsPresentAndTrusted = [&](const std::vector& windowInfos) { auto foundWindowInfo = WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); @@ -427,7 +434,8 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { } return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); }; - windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted); + ASSERT_TRUE( + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted)); } } -- GitLab From 2fc9515b2ae8a4bb4729092c113eff117841a958 Mon Sep 17 00:00:00 2001 From: Patrick Williams Date: Tue, 23 Apr 2024 19:05:38 -0500 Subject: [PATCH 348/465] Fix transaction sanitization Bug: 336648041 Bug: 336648613 Test: CredentialsTest Change-Id: I53894d014bfabc9c958a6f533d7e3b3a6dcd0a34 (cherry picked from commit 04e41761914c3c3aaca965103be3679b7a7af76f) Merged-In: I53894d014bfabc9c958a6f533d7e3b3a6dcd0a34 --- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- .../surfaceflinger/tests/Credentials_test.cpp | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index db205b8a95..d606788053 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -4513,7 +4513,7 @@ status_t SurfaceFlinger::setTransactionState( const int originPid = ipc->getCallingPid(); const int originUid = ipc->getCallingUid(); uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid); - for (auto composerState : states) { + for (auto& composerState : states) { composerState.state.sanitize(permissions); } diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index 69e9a169e3..2d18166da5 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -401,8 +401,13 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { .apply(); } - // Called from non privileged process - Transaction().setTrustedOverlay(surfaceControl, true); + // Attempt to set a trusted overlay from a non-privileged process. This should fail silently. + { + UIDFaker f{AID_BIN}; + Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true); + } + + // Verify that the layer was not made a trusted overlay. { UIDFaker f(AID_SYSTEM); auto windowIsPresentAndNotTrusted = [&](const std::vector& windowInfos) { @@ -413,12 +418,14 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { } return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); }; - windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted); + ASSERT_TRUE( + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted)); } + // Verify that privileged processes are able to set trusted overlays. { UIDFaker f(AID_SYSTEM); - Transaction().setTrustedOverlay(surfaceControl, true); + Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true); auto windowIsPresentAndTrusted = [&](const std::vector& windowInfos) { auto foundWindowInfo = WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos); @@ -427,7 +434,8 @@ TEST_F(CredentialsTest, TransactionPermissionTest) { } return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY); }; - windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted); + ASSERT_TRUE( + windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted)); } } -- GitLab From d53801c5273e34bc767e606454d32aa50f38ece8 Mon Sep 17 00:00:00 2001 From: Alan Ding Date: Wed, 8 May 2024 16:45:29 -0700 Subject: [PATCH 349/465] SF: Cleanup creating / destroying virtual display APIs Follow-up CL for cleanup requested by SF owner in http://ag/27202517. Misc: - Fix type conversion warnings for print format. - Return status destroying virtual display in SF and add appropriate checks in UT. - Use static constexpr / const for compile-time effciciency. Bug: 339525838 Bug: 137375833 Bug: 194863377 Test: atest libsurfaceflinger_unittest Test: atest SurfaceFlinger_test Flag: EXEMPT refactor Change-Id: I7859720989c9244b26f8764c3323a8495064c888 --- libs/gui/SurfaceComposerClient.cpp | 20 ++-- .../aidl/android/gui/ISurfaceComposer.aidl | 4 +- libs/gui/include/gui/ISurfaceComposer.h | 4 +- libs/gui/include/gui/SurfaceComposerClient.h | 8 +- libs/gui/tests/EndToEndNativeInputTest.cpp | 7 +- libs/gui/tests/Surface_test.cpp | 9 +- services/surfaceflinger/SurfaceFlinger.cpp | 41 +++---- services/surfaceflinger/SurfaceFlinger.h | 15 +-- .../surfaceflinger/tests/Credentials_test.cpp | 10 +- .../tests/MultiDisplayLayerBounds_test.cpp | 5 +- .../tests/TransactionTestHarnesses.h | 7 +- .../tests/VirtualDisplay_test.cpp | 5 +- .../SurfaceFlinger_ColorMatrixTest.cpp | 3 +- .../SurfaceFlinger_CreateDisplayTest.cpp | 106 +++++++++--------- .../SurfaceFlinger_DestroyDisplayTest.cpp | 10 +- .../SurfaceFlinger_GetDisplayStatsTest.cpp | 4 +- .../tests/unittests/TestableSurfaceFlinger.h | 20 ++-- 17 files changed, 146 insertions(+), 132 deletions(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7aaaebbc8e..0a85cf8d8a 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1280,18 +1280,22 @@ status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction } // --------------------------------------------------------------------------- -sp SurfaceComposerClient::createDisplay(const String8& displayName, bool isSecure, - const std::string& uniqueId, - float requestedRefreshRate) { +sp SurfaceComposerClient::createVirtualDisplay(const std::string& displayName, + bool isSecure, const std::string& uniqueId, + float requestedRefreshRate) { sp display = nullptr; - binder::Status status = ComposerServiceAIDL::getComposerService() - ->createDisplay(std::string(displayName.c_str()), isSecure, - uniqueId, requestedRefreshRate, &display); + binder::Status status = + ComposerServiceAIDL::getComposerService()->createVirtualDisplay(displayName, isSecure, + uniqueId, + requestedRefreshRate, + &display); return status.isOk() ? display : nullptr; } -void SurfaceComposerClient::destroyDisplay(const sp& display) { - ComposerServiceAIDL::getComposerService()->destroyDisplay(display); +status_t SurfaceComposerClient::destroyVirtualDisplay(const sp& displayToken) { + return ComposerServiceAIDL::getComposerService() + ->destroyVirtualDisplay(displayToken) + .transactionError(); } std::vector SurfaceComposerClient::getPhysicalDisplayIds() { diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index c6e7197f24..11ccc9c2fa 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -105,14 +105,14 @@ interface ISurfaceComposer { * * requires ACCESS_SURFACE_FLINGER permission. */ - @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean isSecure, + @nullable IBinder createVirtualDisplay(@utf8InCpp String displayName, boolean isSecure, @utf8InCpp String uniqueId, float requestedRefreshRate); /** * Destroy a virtual display. * requires ACCESS_SURFACE_FLINGER permission. */ - void destroyDisplay(IBinder display); + void destroyVirtualDisplay(IBinder displayToken); /** * Get stable IDs for connected physical displays. diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h index 738c73a24b..eb4a802c17 100644 --- a/libs/gui/include/gui/ISurfaceComposer.h +++ b/libs/gui/include/gui/ISurfaceComposer.h @@ -130,8 +130,8 @@ public: CREATE_CONNECTION, // Deprecated. Autogenerated by .aidl now. GET_STATIC_DISPLAY_INFO, // Deprecated. Autogenerated by .aidl now. CREATE_DISPLAY_EVENT_CONNECTION, // Deprecated. Autogenerated by .aidl now. - CREATE_DISPLAY, // Deprecated. Autogenerated by .aidl now. - DESTROY_DISPLAY, // Deprecated. Autogenerated by .aidl now. + CREATE_VIRTUAL_DISPLAY, // Deprecated. Autogenerated by .aidl now. + DESTROY_VIRTUAL_DISPLAY, // Deprecated. Autogenerated by .aidl now. GET_PHYSICAL_DISPLAY_TOKEN, // Deprecated. Autogenerated by .aidl now. SET_TRANSACTION_STATE, AUTHENTICATE_SURFACE, // Deprecated. Autogenerated by .aidl now. diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 987efe01ba..e2307ed60a 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -376,11 +376,11 @@ public: sp mirrorDisplay(DisplayId displayId); static const std::string kEmpty; - static sp createDisplay(const String8& displayName, bool isSecure, - const std::string& uniqueId = kEmpty, - float requestedRefreshRate = 0); + static sp createVirtualDisplay(const std::string& displayName, bool isSecure, + const std::string& uniqueId = kEmpty, + float requestedRefreshRate = 0); - static void destroyDisplay(const sp& display); + static status_t destroyVirtualDisplay(const sp& displayToken); static std::vector getPhysicalDisplayIds(); diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp index c0e79655f8..b0db396872 100644 --- a/libs/gui/tests/EndToEndNativeInputTest.cpp +++ b/libs/gui/tests/EndToEndNativeInputTest.cpp @@ -1186,9 +1186,8 @@ public: MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); } void TearDown() override { - for (auto& token : mVirtualDisplays) { - SurfaceComposerClient::destroyDisplay(token); - } + std::for_each(mVirtualDisplays.begin(), mVirtualDisplays.end(), + SurfaceComposerClient::destroyVirtualDisplay); InputSurfacesTest::TearDown(); } @@ -1203,7 +1202,7 @@ public: std::string name = "VirtualDisplay"; name += std::to_string(mVirtualDisplays.size()); - sp token = SurfaceComposerClient::createDisplay(String8(name.c_str()), isSecure); + sp token = SurfaceComposerClient::createVirtualDisplay(name, isSecure); SurfaceComposerClient::Transaction t; t.setDisplaySurface(token, producer); t.setDisplayFlags(token, receivesInput ? 0x01 /* DisplayDevice::eReceivesInput */ : 0); diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index eee4fb93c9..6c6a849544 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -673,13 +673,14 @@ public: return binder::Status::ok(); } - binder::Status createDisplay(const std::string& /*displayName*/, bool /*isSecure*/, - const std::string& /*uniqueId*/, float /*requestedRefreshRate*/, - sp* /*outDisplay*/) override { + binder::Status createVirtualDisplay(const std::string& /*displayName*/, bool /*isSecure*/, + const std::string& /*uniqueId*/, + float /*requestedRefreshRate*/, + sp* /*outDisplay*/) override { return binder::Status::ok(); } - binder::Status destroyDisplay(const sp& /*display*/) override { + binder::Status destroyVirtualDisplay(const sp& /*displayToken*/) override { return binder::Status::ok(); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5181fb8b56..9a575ca769 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -573,8 +574,9 @@ void SurfaceFlinger::run() { mScheduler->run(); } -sp SurfaceFlinger::createDisplay(const String8& displayName, bool isSecure, - const std::string& uniqueId, float requestedRefreshRate) { +sp SurfaceFlinger::createVirtualDisplay(const std::string& displayName, bool isSecure, + const std::string& uniqueId, + float requestedRefreshRate) { // SurfaceComposerAIDL checks for some permissions, but adding an additional check here. // This is to ensure that only root, system, and graphics can request to create a secure // display. Secure displays can show secure content so we add an additional restriction on it. @@ -614,22 +616,23 @@ sp SurfaceFlinger::createDisplay(const String8& displayName, bool isSec return token; } -void SurfaceFlinger::destroyDisplay(const sp& displayToken) { +status_t SurfaceFlinger::destroyVirtualDisplay(const sp& displayToken) { Mutex::Autolock lock(mStateLock); const ssize_t index = mCurrentState.displays.indexOfKey(displayToken); if (index < 0) { ALOGE("%s: Invalid display token %p", __func__, displayToken.get()); - return; + return NAME_NOT_FOUND; } const DisplayDeviceState& state = mCurrentState.displays.valueAt(index); if (state.physical) { ALOGE("%s: Invalid operation on physical display", __func__); - return; + return INVALID_OPERATION; } mCurrentState.displays.removeItemsAt(index); setTransactionFlags(eDisplayTransactionNeeded); + return NO_ERROR; } void SurfaceFlinger::enableHalVirtualDisplays(bool enable) { @@ -6531,18 +6534,19 @@ void SurfaceFlinger::dumpWideColorInfo(std::string& result) const { StringAppendF(&result, "DisplayColorSetting: %s\n", decodeDisplayColorSetting(mDisplayColorSetting).c_str()); - // TODO: print out if wide-color mode is active or not + // TODO: print out if wide-color mode is active or not. for (const auto& [id, display] : mPhysicalDisplays) { StringAppendF(&result, "Display %s color modes:\n", to_string(id).c_str()); for (const auto mode : display.snapshot().colorModes()) { - StringAppendF(&result, " %s (%d)\n", decodeColorMode(mode).c_str(), mode); + StringAppendF(&result, " %s (%d)\n", decodeColorMode(mode).c_str(), + fmt::underlying(mode)); } if (const auto display = getDisplayDeviceLocked(id)) { ui::ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode; StringAppendF(&result, " Current color mode: %s (%d)\n", - decodeColorMode(currentMode).c_str(), currentMode); + decodeColorMode(currentMode).c_str(), fmt::underlying(currentMode)); } } result.append("\n"); @@ -6999,8 +7003,8 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) { // Used by apps to hook Choreographer to SurfaceFlinger. case CREATE_DISPLAY_EVENT_CONNECTION: case CREATE_CONNECTION: - case CREATE_DISPLAY: - case DESTROY_DISPLAY: + case CREATE_VIRTUAL_DISPLAY: + case DESTROY_VIRTUAL_DISPLAY: case GET_PRIMARY_PHYSICAL_DISPLAY_ID: case GET_PHYSICAL_DISPLAY_IDS: case GET_PHYSICAL_DISPLAY_TOKEN: @@ -9553,26 +9557,25 @@ binder::Status SurfaceComposerAIDL::createConnection(sp* outDisplay) { +binder::Status SurfaceComposerAIDL::createVirtualDisplay(const std::string& displayName, + bool isSecure, const std::string& uniqueId, + float requestedRefreshRate, + sp* outDisplay) { status_t status = checkAccessPermission(); if (status != OK) { return binderStatusFromStatusT(status); } - String8 displayName8 = String8::format("%s", displayName.c_str()); - *outDisplay = mFlinger->createDisplay(displayName8, isSecure, uniqueId, requestedRefreshRate); + *outDisplay = + mFlinger->createVirtualDisplay(displayName, isSecure, uniqueId, requestedRefreshRate); return binder::Status::ok(); } -binder::Status SurfaceComposerAIDL::destroyDisplay(const sp& display) { +binder::Status SurfaceComposerAIDL::destroyVirtualDisplay(const sp& displayToken) { status_t status = checkAccessPermission(); if (status != OK) { return binderStatusFromStatusT(status); } - mFlinger->destroyDisplay(display); - return binder::Status::ok(); + return binder::Status::fromStatusT(mFlinger->destroyVirtualDisplay(displayToken)); } binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector* outDisplayIds) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 84745150d0..2363cd7ebd 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -547,9 +547,10 @@ private: status_t dump(int fd, const Vector& args) override { return priorityDump(fd, args); } // ISurfaceComposer implementation: - sp createDisplay(const String8& displayName, bool isSecure, - const std::string& uniqueId, float requestedRefreshRate = 0.0f); - void destroyDisplay(const sp& displayToken); + sp createVirtualDisplay(const std::string& displayName, bool isSecure, + const std::string& uniqueId, + float requestedRefreshRate = 0.0f); + status_t destroyVirtualDisplay(const sp& displayToken); std::vector getPhysicalDisplayIds() const EXCLUDES(mStateLock) { Mutex::Autolock lock(mStateLock); return getPhysicalDisplayIdsLocked(); @@ -1568,10 +1569,10 @@ public: const sp& layerHandle, sp* outConnection) override; binder::Status createConnection(sp* outClient) override; - binder::Status createDisplay(const std::string& displayName, bool isSecure, - const std::string& uniqueId, float requestedRefreshRate, - sp* outDisplay) override; - binder::Status destroyDisplay(const sp& display) override; + binder::Status createVirtualDisplay(const std::string& displayName, bool isSecure, + const std::string& uniqueId, float requestedRefreshRate, + sp* outDisplay) override; + binder::Status destroyVirtualDisplay(const sp& displayToken) override; binder::Status getPhysicalDisplayIds(std::vector* outDisplayIds) override; binder::Status getPhysicalDisplayToken(int64_t displayId, sp* outDisplay) override; binder::Status setPowerMode(const sp& display, int mode) override; diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp index c1ef48ec97..ebe11fb0f3 100644 --- a/services/surfaceflinger/tests/Credentials_test.cpp +++ b/services/surfaceflinger/tests/Credentials_test.cpp @@ -39,8 +39,8 @@ using gui::aidl_utils::statusTFromBinderStatus; using ui::ColorMode; namespace { -const String8 DISPLAY_NAME("Credentials Display Test"); -const String8 SURFACE_NAME("Test Surface Name"); +const std::string kDisplayName("Credentials Display Test"); +const String8 kSurfaceName("Test Surface Name"); } // namespace /** @@ -99,7 +99,7 @@ protected: ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplay, &mode)); // Background surface - mBGSurfaceControl = mComposerClient->createSurface(SURFACE_NAME, mode.resolution.getWidth(), + mBGSurfaceControl = mComposerClient->createSurface(kSurfaceName, mode.resolution.getWidth(), mode.resolution.getHeight(), PIXEL_FORMAT_RGBA_8888, 0); ASSERT_TRUE(mBGSurfaceControl != nullptr); @@ -232,7 +232,7 @@ TEST_F(CredentialsTest, SetActiveColorModeTest) { TEST_F(CredentialsTest, CreateDisplayTest) { // Only graphics and system processes can create a secure display. std::function condition = [=]() { - sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, true); + sp testDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, true); return testDisplay.get() != nullptr; }; @@ -267,7 +267,7 @@ TEST_F(CredentialsTest, CreateDisplayTest) { } condition = [=]() { - sp testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false); + sp testDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, false); return testDisplay.get() != nullptr; }; ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false)); diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp index 7fce7e9af9..56cf13d7fe 100644 --- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp +++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp @@ -53,14 +53,15 @@ protected: } virtual void TearDown() { - SurfaceComposerClient::destroyDisplay(mVirtualDisplay); + EXPECT_EQ(NO_ERROR, SurfaceComposerClient::destroyVirtualDisplay(mVirtualDisplay)); LayerTransactionTest::TearDown(); mColorLayer = 0; } void createDisplay(const ui::Size& layerStackSize, ui::LayerStack layerStack) { + static const std::string kDisplayName("VirtualDisplay"); mVirtualDisplay = - SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/); + SurfaceComposerClient::createVirtualDisplay(kDisplayName, false /*isSecure*/); asTransaction([&](Transaction& t) { t.setDisplaySurface(mVirtualDisplay, mProducer); t.setDisplayLayerStack(mVirtualDisplay, layerStack); diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h index 87e6d3ee9f..af3cb9aec1 100644 --- a/services/surfaceflinger/tests/TransactionTestHarnesses.h +++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h @@ -66,8 +66,9 @@ public: sp listener = sp::make(this); itemConsumer->setFrameAvailableListener(listener); - vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), - false /*secure*/); + static const std::string kDisplayName("VirtualDisplay"); + vDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, + false /*isSecure*/); constexpr ui::LayerStack layerStack{ 848472}; // ASCII for TTH (TransactionTestHarnesses) @@ -107,7 +108,7 @@ public: t.setLayerStack(mirrorSc, ui::INVALID_LAYER_STACK); t.apply(true); } - SurfaceComposerClient::destroyDisplay(vDisplay); + SurfaceComposerClient::destroyVirtualDisplay(vDisplay); return sc; } } diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp index f31f582ffd..cd66dd20bb 100644 --- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp +++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp @@ -41,14 +41,15 @@ protected: }; TEST_F(VirtualDisplayTest, VirtualDisplayDestroyedSurfaceReuse) { + static const std::string kDisplayName("VirtualDisplay"); sp virtualDisplay = - SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/); + SurfaceComposerClient::createVirtualDisplay(kDisplayName, false /*isSecure*/); SurfaceComposerClient::Transaction t; t.setDisplaySurface(virtualDisplay, mProducer); t.apply(true); - SurfaceComposerClient::destroyDisplay(virtualDisplay); + EXPECT_EQ(NO_ERROR, SurfaceComposerClient::destroyVirtualDisplay(virtualDisplay)); virtualDisplay.clear(); // Sync here to ensure the display was completely destroyed in SF t.apply(true); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp index 5852b1c45f..ff7612e064 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp @@ -53,7 +53,8 @@ TEST_F(ColorMatrixTest, colorMatrixChangedAfterDisplayTransaction) { mFlinger.commitAndComposite(); EXPECT_COLOR_MATRIX_CHANGED(false, false); - mFlinger.createDisplay(String8("Test Display"), false); + static const std::string kDisplayName("Test Display"); + mFlinger.createVirtualDisplay(kDisplayName, false /*isSecure=*/); mFlinger.commit(); EXPECT_COLOR_MATRIX_CHANGED(false, true); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp index bf5ae21f60..e5f2a9138d 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp @@ -27,7 +27,7 @@ namespace { class CreateDisplayTest : public DisplayTransactionTest { public: - void createDisplayWithRequestedRefreshRate(const String8& name, uint64_t displayId, + void createDisplayWithRequestedRefreshRate(const std::string& name, uint64_t displayId, float pacesetterDisplayRefreshRate, float requestedRefreshRate, float expectedAdjustedRefreshRate) { @@ -37,7 +37,7 @@ public: // -------------------------------------------------------------------- // Invocation - sp displayToken = mFlinger.createDisplay(name, false, requestedRefreshRate); + sp displayToken = mFlinger.createVirtualDisplay(name, false, requestedRefreshRate); // -------------------------------------------------------------------- // Postconditions @@ -73,7 +73,7 @@ public: }; TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) { - const String8 name("virtual.test"); + static const std::string name("virtual.test"); // -------------------------------------------------------------------- // Call Expectations @@ -81,7 +81,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) { // -------------------------------------------------------------------- // Invocation - sp displayToken = mFlinger.createDisplay(name, false); + sp displayToken = mFlinger.createVirtualDisplay(name, false); // -------------------------------------------------------------------- // Postconditions @@ -101,7 +101,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) { } TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { - const String8 name("virtual.test"); + static const std::string kDisplayName("virtual.test"); // -------------------------------------------------------------------- // Call Expectations @@ -112,7 +112,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { // Set the calling identity to graphics so captureDisplay with secure is allowed. IPCThreadState::self()->restoreCallingIdentity(static_cast(AID_GRAPHICS) << 32 | AID_GRAPHICS); - sp displayToken = mFlinger.createDisplay(name, true); + sp displayToken = mFlinger.createVirtualDisplay(kDisplayName, true); IPCThreadState::self()->restoreCallingIdentity(oldId); // -------------------------------------------------------------------- @@ -123,7 +123,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { const auto& display = getCurrentDisplayState(displayToken); EXPECT_TRUE(display.isVirtual()); EXPECT_TRUE(display.isSecure); - EXPECT_EQ(name.c_str(), display.displayName); + EXPECT_EQ(kDisplayName.c_str(), display.displayName); // -------------------------------------------------------------------- // Cleanup conditions @@ -133,8 +133,8 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) { } TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) { - const String8 name("virtual.test"); - const std::string uniqueId = "virtual:package:id"; + static const std::string kDisplayName("virtual.test"); + static const std::string kUniqueId = "virtual:package:id"; // -------------------------------------------------------------------- // Call Expectations @@ -142,7 +142,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) { // -------------------------------------------------------------------- // Invocation - sp displayToken = mFlinger.createDisplay(name, false, uniqueId); + sp displayToken = mFlinger.createVirtualDisplay(kDisplayName, false, kUniqueId); // -------------------------------------------------------------------- // Postconditions @@ -153,7 +153,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) { EXPECT_TRUE(display.isVirtual()); EXPECT_FALSE(display.isSecure); EXPECT_EQ(display.uniqueId, "virtual:package:id"); - EXPECT_EQ(name.c_str(), display.displayName); + EXPECT_EQ(kDisplayName.c_str(), display.displayName); // -------------------------------------------------------------------- // Cleanup conditions @@ -164,78 +164,78 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) { // Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRate0) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 60.f; - const float kRequestedRefreshRate = 0.f; - const float kExpectedAdjustedRefreshRate = 0.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 60.f; + constexpr float kRequestedRefreshRate = 0.f; + constexpr float kExpectedAdjustedRefreshRate = 0.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } // Requesting negative refresh rate, will be ignored, same as requesting 0 TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNegative) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 60.f; - const float kRequestedRefreshRate = -60.f; - const float kExpectedAdjustedRefreshRate = 0.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 60.f; + constexpr float kRequestedRefreshRate = -60.f; + constexpr float kExpectedAdjustedRefreshRate = 0.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } // Requesting a higher refresh rate than the pacesetter TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateHigh) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 60.f; - const float kRequestedRefreshRate = 90.f; - const float kExpectedAdjustedRefreshRate = 60.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 60.f; + constexpr float kRequestedRefreshRate = 90.f; + constexpr float kExpectedAdjustedRefreshRate = 60.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } // Requesting the same refresh rate as the pacesetter TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateSame) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 60.f; - const float kRequestedRefreshRate = 60.f; - const float kExpectedAdjustedRefreshRate = 60.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 60.f; + constexpr float kRequestedRefreshRate = 60.f; + constexpr float kExpectedAdjustedRefreshRate = 60.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } // Requesting a divisor (30) of the pacesetter (60) should be honored TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateDivisor) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 60.f; - const float kRequestedRefreshRate = 30.f; - const float kExpectedAdjustedRefreshRate = 30.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 60.f; + constexpr float kRequestedRefreshRate = 30.f; + constexpr float kExpectedAdjustedRefreshRate = 30.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } // Requesting a non divisor (45) of the pacesetter (120) should round up to a divisor (60) TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisor) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 120.f; - const float kRequestedRefreshRate = 45.f; - const float kExpectedAdjustedRefreshRate = 60.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 120.f; + constexpr float kRequestedRefreshRate = 45.f; + constexpr float kExpectedAdjustedRefreshRate = 60.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } // Requesting a non divisor (75) of the pacesetter (120) should round up to pacesetter (120) TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisorMax) { - const String8 displayName("virtual.test"); - const uint64_t displayId = 123ull; - const float kPacesetterDisplayRefreshRate = 120.f; - const float kRequestedRefreshRate = 75.f; - const float kExpectedAdjustedRefreshRate = 120.f; - createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate, + static const std::string kDisplayName("virtual.test"); + constexpr uint64_t kDisplayId = 123ull; + constexpr float kPacesetterDisplayRefreshRate = 120.f; + constexpr float kRequestedRefreshRate = 75.f; + constexpr float kExpectedAdjustedRefreshRate = 120.f; + createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate, kRequestedRefreshRate, kExpectedAdjustedRefreshRate); } diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp index 93a3811172..f8ad8e1e1b 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp @@ -43,18 +43,18 @@ TEST_F(DestroyDisplayTest, destroyDisplayClearsCurrentStateForDisplay) { // -------------------------------------------------------------------- // Invocation - mFlinger.destroyDisplay(existing.token()); + EXPECT_EQ(NO_ERROR, mFlinger.destroyVirtualDisplay(existing.token())); // -------------------------------------------------------------------- // Postconditions - // The display should have been removed from the current state + // The display should have been removed from the current state. EXPECT_FALSE(hasCurrentDisplayState(existing.token())); - // Ths display should still exist in the drawing state + // Ths display should still exist in the drawing state. EXPECT_TRUE(hasDrawingDisplayState(existing.token())); - // The display transaction needed flasg should be set + // The display transaction needed flags should be set. EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded)); } @@ -67,7 +67,7 @@ TEST_F(DestroyDisplayTest, destroyDisplayHandlesUnknownDisplay) { // -------------------------------------------------------------------- // Invocation - mFlinger.destroyDisplay(displayToken); + EXPECT_EQ(NAME_NOT_FOUND, mFlinger.destroyVirtualDisplay(displayToken)); } } // namespace diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp index 4e9fba7bda..f424133655 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp @@ -42,8 +42,8 @@ TEST_F(SurfaceFlingerGetDisplayStatsTest, explicitToken) { } TEST_F(SurfaceFlingerGetDisplayStatsTest, invalidToken) { - const String8 displayName("fakeDisplay"); - sp displayToken = mFlinger.createDisplay(displayName, false); + static const std::string kDisplayName("fakeDisplay"); + sp displayToken = mFlinger.createVirtualDisplay(kDisplayName, false /*isSecure*/); DisplayStatInfo info; status_t status = mFlinger.getDisplayStats(displayToken, &info); EXPECT_EQ(status, NAME_NOT_FOUND); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index b251e2cf0c..265f804fc8 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -420,19 +420,21 @@ public: commit(kComposite); } - auto createDisplay(const String8& displayName, bool isSecure, - float requestedRefreshRate = 0.0f) { - const std::string testId = "virtual:libsurfaceflinger_unittest:TestableSurfaceFlinger"; - return mFlinger->createDisplay(displayName, isSecure, testId, requestedRefreshRate); + auto createVirtualDisplay(const std::string& displayName, bool isSecure, + float requestedRefreshRate = 0.0f) { + static const std::string kTestId = + "virtual:libsurfaceflinger_unittest:TestableSurfaceFlinger"; + return mFlinger->createVirtualDisplay(displayName, isSecure, kTestId, requestedRefreshRate); } - auto createDisplay(const String8& displayName, bool isSecure, const std::string& uniqueId, - float requestedRefreshRate = 0.0f) { - return mFlinger->createDisplay(displayName, isSecure, uniqueId, requestedRefreshRate); + auto createVirtualDisplay(const std::string& displayName, bool isSecure, + const std::string& uniqueId, float requestedRefreshRate = 0.0f) { + return mFlinger->createVirtualDisplay(displayName, isSecure, uniqueId, + requestedRefreshRate); } - auto destroyDisplay(const sp& displayToken) { - return mFlinger->destroyDisplay(displayToken); + auto destroyVirtualDisplay(const sp& displayToken) { + return mFlinger->destroyVirtualDisplay(displayToken); } auto getDisplay(const sp& displayToken) { -- GitLab From 6431d4afddc338a3e8a997f341df5a23d54efbc7 Mon Sep 17 00:00:00 2001 From: Nergi Rahardi Date: Wed, 15 May 2024 18:42:48 +0900 Subject: [PATCH 350/465] Set Change::Metadata on LayerMetadata update Currently, on layer_state_t::eMetadataChanged, only Change::GameMode is set. Hence, for other metadata changes such as METADATA_OWNER_UID, METADATA_MOUSE_CURSOR, etc. (used by ARC++), these doesn't get propagated until `forceUpdate=true` or `gameMode` is updated. This leaves ARC++ LayerFE receiving stale metadata, even though "update" was received. Bug: 339382668 Test: atest libsurfaceflinger_unittests Change-Id: I8130c3505b9808a9aa04d1641dc1c22352d6bf87 --- libs/gui/include/gui/LayerMetadata.h | 1 + .../FrontEnd/LayerSnapshotBuilder.cpp | 10 ++-- .../FrontEnd/RequestedLayerState.cpp | 1 + .../tests/unittests/LayerSnapshotTest.cpp | 46 ++++++++++++++++++- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h index 9cf62bc7d6..7ee291df4c 100644 --- a/libs/gui/include/gui/LayerMetadata.h +++ b/libs/gui/include/gui/LayerMetadata.h @@ -74,6 +74,7 @@ enum class GameMode : int32_t { } // namespace android::gui using android::gui::METADATA_ACCESSIBILITY_ID; +using android::gui::METADATA_CALLING_UID; using android::gui::METADATA_DEQUEUE_TIME; using android::gui::METADATA_GAME_MODE; using android::gui::METADATA_MOUSE_CURSOR; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index f2497d4bc8..caeb575c4e 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -785,10 +785,12 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } } - if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { - snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) - ? requested.gameMode - : parentSnapshot.gameMode; + if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) { + if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { + snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) + ? requested.gameMode + : parentSnapshot.gameMode; + } updateMetadata(snapshot, requested, args); if (args.includeMetadata) { snapshot.layerMetadata = parentSnapshot.layerMetadata; diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 028bd19a60..5631facdae 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -328,6 +328,7 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta changes |= RequestedLayerState::Changes::GameMode; } } + changes |= RequestedLayerState::Changes::Metadata; } if (clientState.what & layer_state_t::eFrameRateChanged) { const auto compatibility = diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 82adadc368..5ff6417482 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -278,13 +278,57 @@ TEST_F(LayerSnapshotTest, GameMode) { transactions.back().states.front().layerId = 1; transactions.back().states.front().state.layerId = static_cast(1); mLifecycleManager.applyTransactions(transactions); - EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode); + EXPECT_EQ(mLifecycleManager.getGlobalChanges(), + RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged); EXPECT_EQ(static_cast(getSnapshot(1)->gameMode), 42); EXPECT_EQ(static_cast(getSnapshot(11)->gameMode), 42); } +TEST_F(LayerSnapshotTest, UpdateMetadata) { + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + transactions.back().states.front().state.what = layer_state_t::eMetadataChanged; + // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly, + // and not using stale data. + transactions.back().states.front().state.metadata = LayerMetadata(); + transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123); + transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234); + transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345); + transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456); + transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567); + transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678); + transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789); + + transactions.back().states.front().layerId = 1; + transactions.back().states.front().state.layerId = static_cast(1); + + mLifecycleManager.applyTransactions(transactions); + EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Metadata); + + // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = true, + .displays = mFrontEndDisplayInfos, + .globalShadowSettings = globalShadowSettings, + .supportsBlur = true, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; + update(mSnapshotBuilder, args); + + EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789); +} + TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { // ROOT // ├── 1 -- GitLab From da9ba7b2325e53435cbc0feb8470b0c3e3affaf8 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Mon, 13 May 2024 20:13:06 +0000 Subject: [PATCH 351/465] Cleanup old boolean constexprs. We used these as flags before we had flags, and these have been true for about a year now. Bug: 329464641 Test: builds Change-Id: I8de3a05ec245788a5aa0b99d28aa36a678d12cbc --- libs/renderengine/skia/SkiaRenderEngine.cpp | 4 +--- services/surfaceflinger/SurfaceFlinger.cpp | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 325a91178a..b973211966 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -91,7 +91,6 @@ namespace { // Debugging settings static const bool kPrintLayerSettings = false; static const bool kGaneshFlushAfterEveryLayer = kPrintLayerSettings; -static constexpr bool kEnableLayerBrightening = true; } // namespace @@ -714,8 +713,7 @@ void SkiaRenderEngine::drawLayersInternal( // ...and compute the dimming ratio if dimming is requested const float displayDimmingRatio = display.targetLuminanceNits > 0.f && - maxLayerWhitePoint > 0.f && - (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint) + maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint ? maxLayerWhitePoint / display.targetLuminanceNits : 1.f; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 33dbab01df..9edf90414f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8334,10 +8334,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( renderArea->getHintForSeamlessTransition()); sdrWhitePointNits = state.sdrWhitePointNits; - // TODO(b/298219334): Clean this up once we verify this doesn't break anything - static constexpr bool kScreenshotsDontDim = true; - - if (kScreenshotsDontDim && !captureResults.capturedHdrLayers) { + if (!captureResults.capturedHdrLayers) { displayBrightnessNits = sdrWhitePointNits; } else { displayBrightnessNits = state.displayBrightnessNits; -- GitLab From 1818c1861f9d1dd16a3db21260a21ba38790aad5 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Mon, 13 May 2024 18:25:11 +0000 Subject: [PATCH 352/465] Add flag for enabling local tonemapping screenshots Bug: 329464641 Test: builds Change-Id: I6051ccc1eb1cbf3105c66f5ccb31bd67f9d6e7ec --- services/surfaceflinger/common/FlagManager.cpp | 2 ++ .../surfaceflinger/common/include/common/FlagManager.h | 1 + services/surfaceflinger/surfaceflinger_flags_new.aconfig | 8 ++++++++ 3 files changed, 11 insertions(+) diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index e73cf5d785..0feacacd80 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -145,6 +145,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter); DUMP_READ_ONLY_FLAG(detached_mirror); DUMP_READ_ONLY_FLAG(commit_not_composited); + DUMP_READ_ONLY_FLAG(local_tonemap_screenshots); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -240,6 +241,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, ""); FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, ""); FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, ""); FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, ""); +FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots"); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 327cc4ae41..5850fe1b02 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -84,6 +84,7 @@ public: bool allow_n_vsyncs_in_targeter() const; bool detached_mirror() const; bool commit_not_composited() const; + bool local_tonemap_screenshots() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index f1ec3e1ec2..04f1d5080a 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -76,4 +76,12 @@ flag { } } # latch_unsignaled_with_auto_refresh_changed +flag { + name: "local_tonemap_screenshots" + namespace: "core_graphics" + description: "Enables local tonemapping when capturing screenshots" + bug: "329464641" + is_fixed_read_only: true +} # local_tonemap_screenshots + # IMPORTANT - please keep alphabetize to reduce merge conflicts -- GitLab From 086507b2f7b087f0dea152cc617d9c991789820b Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 16 May 2024 15:33:16 -0400 Subject: [PATCH 353/465] SF: Skip choosing mode for powered-off followers While the inactive display of a foldable is powered off, the Scheduler's chosen mode for it is ignored by shouldApplyRefreshRateSelectorPolicy in SF::requestDisplayModes, as it will be applied when the display becomes active. As an optimization, skip RefreshRateSelector::getRankedFrameRates for follower displays, since SF::requestDisplayModes throws the result away. This also restricts the mode of powered-off external displays to only be set in response to DM policy changes, e.g. 60+60 constraint. Previously, the Scheduler could initiate a mode request with an unconstrained policy before powering on the display, which HWC would apply after powering on. Flag: NONE (blocks P1 bug fix, and risk is limited to multi-display) Bug: 329111930 Bug: 318534874 Test: SchedulerTest.chooseDisplayModesMultipleDisplays Change-Id: I4dd954bf9a943f181bd64950ff9edee863f53e99 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 2 ++ services/surfaceflinger/tests/unittests/SchedulerTest.cpp | 4 ++++ services/surfaceflinger/tests/unittests/TestableScheduler.h | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 005ec05aab..30bd73533a 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -1152,8 +1152,10 @@ auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap { return pacesetterFps; }(); + // Choose a mode for powered-on follower displays. for (const auto& [id, display] : mDisplays) { if (id == *mPacesetterDisplayId) continue; + if (display.powerMode != hal::PowerMode::ON) continue; auto rankedFrameRates = display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals, diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 7479a4f890..4fb06907d0 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -350,6 +350,9 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { std::make_shared(kDisplay2Modes, kDisplay2Mode60->getId())); + mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON); + mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON); + using DisplayModeChoice = TestableScheduler::DisplayModeChoice; TestableScheduler::DisplayModeChoiceMap expectedChoices; @@ -412,6 +415,7 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { ->registerDisplay(kDisplayId3, std::make_shared(kDisplay3Modes, kDisplay3Mode60->getId())); + mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON); const GlobalSignals globalSignals = {.touch = true}; mScheduler->replaceTouchTimer(10); diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 1e02c67d91..198a5def8c 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -111,6 +111,11 @@ public: Scheduler::unregisterDisplay(displayId); } + void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) { + ftl::FakeGuard guard(kMainThreadContext); + Scheduler::setDisplayPowerMode(displayId, powerMode); + } + std::optional pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS { return mPacesetterDisplayId; } -- GitLab From 1746afd8641626925d9b6a1994a0ec13f6bf5c15 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Wed, 10 Apr 2024 19:21:37 -0400 Subject: [PATCH 354/465] SF: Stop forcing modeset on fold/unfold SF no longer needs to defer setDesiredDisplayModeSpecs for the inactive display until it becomes active on fold/unfold, because it now supports per-display modeset since I9da3a0be07f9fbb08f11485aa6ab9400259a4e09. Remove SF::shouldApplyRefreshRateSelectorPolicy, which was used to skip applying DM's policy while a display is powered off. Though it had also been used to skip SF::requestDisplayModes in response to changes in the Scheduler's policy, I4dd954bf9a943f181bd64950ff9edee863f53e99 suppressed those calls in the Scheduler. Bug: 255635711 Bug: 255635821 Flag: NONE (blocks P1 bug fix, and perf risk is limited to foldables) Test: Watch refresh rate overlay for MRR CUJs plus folding/unfolding. Test: DisplayModeSwitchingTest Change-Id: Ia5afb75977249fc734bd78625d8ffd4c433a3a8d --- services/surfaceflinger/SurfaceFlinger.cpp | 55 ++----------- services/surfaceflinger/SurfaceFlinger.h | 6 +- .../SurfaceFlinger_DisplayModeSwitching.cpp | 78 ++++++++++--------- 3 files changed, 49 insertions(+), 90 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b3ddb57472..252ce4d2f1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1425,11 +1425,6 @@ void SurfaceFlinger::initiateDisplayModeChanges() { continue; } - if (!shouldApplyRefreshRateSelectorPolicy(*display)) { - dropModeRequest(display); - continue; - } - const auto desiredModeId = desiredModeOpt->mode.modePtr->getId(); const auto displayModePtrOpt = physical.snapshot().displayModes().get(desiredModeId); @@ -4228,12 +4223,6 @@ void SurfaceFlinger::requestDisplayModes(std::vectorrefreshRateSelector().isModeAllowed(request.mode)) { setDesiredMode(std::move(request)); } else { @@ -8562,35 +8551,11 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal( break; } - if (!shouldApplyRefreshRateSelectorPolicy(*display)) { - ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str()); - return NO_ERROR; - } - return applyRefreshRateSelectorPolicy(displayId, selector); } -bool SurfaceFlinger::shouldApplyRefreshRateSelectorPolicy(const DisplayDevice& display) const { - if (display.isPoweredOn() || mPhysicalDisplays.size() == 1) return true; - - LOG_ALWAYS_FATAL_IF(display.isVirtual()); - const auto displayId = display.getPhysicalId(); - - // The display is powered off, and this is a multi-display device. If the display is the - // inactive internal display of a dual-display foldable, then the policy will be applied - // when it becomes active upon powering on. - // - // TODO(b/255635711): Remove this function (i.e. returning `false` as a special case) once - // concurrent mode setting across multiple (potentially powered off) displays is supported. - // - return displayId == mActiveDisplayId || - !mPhysicalDisplays.get(displayId) - .transform(&PhysicalDisplay::isInternal) - .value_or(false); -} - status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( - PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) { + PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) { const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy(); ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str()); @@ -8621,7 +8586,7 @@ status_t SurfaceFlinger::applyRefreshRateSelectorPolicy( return INVALID_OPERATION; } - setDesiredMode({std::move(preferredMode), .emitEvent = true, .force = force}); + setDesiredMode({std::move(preferredMode), .emitEvent = true}); // Update the frameRateOverride list as the display render rate might have changed if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) { @@ -8979,13 +8944,8 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD const DisplayDevice& activeDisplay) { ATRACE_CALL(); - // For the first display activated during boot, there is no need to force setDesiredMode, - // because DM is about to send its policy via setDesiredDisplayModeSpecs. - bool forceApplyPolicy = false; - if (inactiveDisplayPtr) { inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false); - forceApplyPolicy = true; } mActiveDisplayId = activeDisplay.getPhysicalId(); @@ -9002,12 +8962,11 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD mActiveDisplayTransformHint = activeDisplay.getTransformHint(); sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation()); - // The policy of the new active/pacesetter display may have changed while it was inactive. In - // that case, its preferred mode has not been propagated to HWC (via setDesiredMode). In either - // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode, - // and the kernel idle timer of the newly active display must be toggled. - applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(), - forceApplyPolicy); + // Whether or not the policy of the new active/pacesetter display changed while it was inactive + // (in which case its preferred mode has already been propagated to HWC via setDesiredMode), the + // Scheduler's cachedModeChangedParams must be initialized to the newly active mode, and the + // kernel idle timer of the newly active display must be toggled. + applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector()); } status_t SurfaceFlinger::addWindowInfosListener(const sp& windowInfosListener, diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 84745150d0..3c76329345 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -754,13 +754,9 @@ private: const sp&, const scheduler::RefreshRateSelector::PolicyVariant&) EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); - bool shouldApplyRefreshRateSelectorPolicy(const DisplayDevice&) const - REQUIRES(mStateLock, kMainThreadContext); - // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter. status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId, - const scheduler::RefreshRateSelector&, - bool force = false) + const scheduler::RefreshRateSelector&) REQUIRES(mStateLock, kMainThreadContext); void commitTransactionsLegacy() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 15a6db626a..078b4fe15e 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -327,7 +327,9 @@ MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") { return false; } - if (!flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) { + // VsyncModulator should react to mode switches on the pacesetter display. + if (arg->getPhysicalId() == flinger->scheduler()->pacesetterDisplayId() && + !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) { *result_listener << "VsyncModulator did not shift to early phase"; return false; } @@ -368,8 +370,8 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); - // Only the inner display is powered on. - mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay); + mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF); + mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); @@ -385,40 +387,44 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { 0.f, 120.f))); EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); + EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); mFlinger.commit(); EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); mFlinger.commit(); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); - - innerDisplay->setPowerMode(hal::PowerMode::OFF); - outerDisplay->setPowerMode(hal::PowerMode::ON); + EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); - // Only the outer display is powered on. - mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay); + mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); - EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId60, false, + 0.f, 120.f))); + + EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); + EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId60); mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); + EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); + EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); } @@ -437,10 +443,8 @@ TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); - outerDisplay->setPowerMode(hal::PowerMode::ON); - - // Both displays are powered on. - mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay); + mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON); + mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); @@ -485,7 +489,7 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); // Power off the display before the mode has been set. - mDisplay->setPowerMode(hal::PowerMode::OFF); + mFlinger.setPowerModeInternal(mDisplay, hal::PowerMode::OFF); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); @@ -517,10 +521,8 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); - outerDisplay->setPowerMode(hal::PowerMode::ON); - - // Both displays are powered on. - mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay); + mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON); + mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); @@ -539,40 +541,42 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); // Power off the outer display before the mode has been set. - outerDisplay->setPowerMode(hal::PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF); const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90); + EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); mFlinger.commit(); - // Powering off the inactive display should abort the mode set. + // Powering off the inactive display should not abort the mode set. EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); mFlinger.commit(); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); - innerDisplay->setPowerMode(hal::PowerMode::OFF); - outerDisplay->setPowerMode(hal::PowerMode::ON); + mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF); + mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); - // Only the outer display is powered on. - mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay); + EXPECT_EQ(NO_ERROR, + mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(), + mock::createDisplayModeSpecs(kModeId120, false, + 0.f, 120.f))); - EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60); + EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId120); mFlinger.commit(); - // The mode set should resume once the display becomes active. EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); + EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId120)); mFlinger.commit(); EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); } } // namespace -- GitLab From df59f4744c0672cc69dad72b230a757c1e4be116 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Fri, 17 May 2024 16:51:33 +0000 Subject: [PATCH 355/465] SF: avoid a composition cycle when the FrameRate votes updates SF would still do a composition if the display mode changes, but it is not required for every frame rate vote change. Bug: 339759346 Test: android.platform.test.scenario.gmail.OpenCloseComposeEmailMicrobenchmark#testOpenCloseComposeEmail Change-Id: Ia01a13b1a167b3a0a67cf7be3db5e64b9405580a --- libs/gui/include/gui/LayerState.h | 3 ++ .../FrontEnd/LayerLifecycleManager.cpp | 18 ++++++++---- .../FrontEnd/RequestedLayerState.cpp | 8 ++++-- .../FrontEnd/RequestedLayerState.h | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 20 ++++++++++--- services/surfaceflinger/SurfaceFlinger.h | 2 +- .../surfaceflinger/common/FlagManager.cpp | 2 ++ .../common/include/common/FlagManager.h | 1 + .../surfaceflinger_flags_new.aconfig | 11 ++++++++ .../unittests/LayerLifecycleManagerTest.cpp | 28 +++++++++++-------- .../tests/unittests/LayerSnapshotTest.cpp | 8 ++++-- 11 files changed, 76 insertions(+), 26 deletions(-) diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index ca7acf9be4..ebdf2326d6 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -279,6 +279,9 @@ struct layer_state_t { layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged | layer_state_t::eLayerStackChanged; + // Changes requiring a composition pass. + static constexpr uint64_t REQUIRES_COMPOSITION = layer_state_t::CONTENT_DIRTY; + // Changes that affect the visible region on a display. static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged; diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 4b0618e5aa..52f8bea4ba 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -41,7 +41,8 @@ void LayerLifecycleManager::addLayers(std::vectorparentId == layer.id) { linkedLayer->parentId = UNASSIGNED_LAYER_ID; if (linkedLayer->canBeDestroyed()) { - linkedLayer->changes |= RequestedLayerState::Changes::Destroyed; + linkedLayer->changes |= RequestedLayerState::Changes::Destroyed | + RequestedLayerState::Changes::RequiresComposition; layersToBeDestroyed.emplace_back(linkedLayer->id); } } @@ -249,7 +253,8 @@ void LayerLifecycleManager::applyTransactions(const std::vectorchanges |= RequestedLayerState::Changes::Content; mChangedLayers.push_back(bgColorLayer); - mGlobalChanges |= RequestedLayerState::Changes::Content; + mGlobalChanges |= RequestedLayerState::Changes::Content | + RequestedLayerState::Changes::RequiresComposition; } } @@ -407,7 +412,8 @@ void LayerLifecycleManager::fixRelativeZLoop(uint32_t relativeRootId) { layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); layer.changes |= RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::RelativeParent; - mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; + mGlobalChanges |= RequestedLayerState::Changes::Hierarchy | + RequestedLayerState::Changes::RequiresComposition; } // Some layers mirror the entire display stack. Since we don't have a single root layer per display diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 5631facdae..f5e5b0233e 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -58,7 +58,8 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) parentId(args.parentId), layerIdToMirror(args.layerIdToMirror) { layerId = static_cast(args.sequence); - changes |= RequestedLayerState::Changes::Created; + changes |= RequestedLayerState::Changes::Created | + RequestedLayerState::Changes::RequiresComposition; metadata.merge(args.metadata); changes |= RequestedLayerState::Changes::Metadata; handleAlive = true; @@ -248,7 +249,8 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if (hadSomethingToDraw != hasSomethingToDraw()) { changes |= RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::VisibleRegion; + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::RequiresComposition; } if (clientChanges & layer_state_t::HIERARCHY_CHANGES) changes |= RequestedLayerState::Changes::Hierarchy; @@ -258,6 +260,8 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta changes |= RequestedLayerState::Changes::Geometry; if (clientChanges & layer_state_t::AFFECTS_CHILDREN) changes |= RequestedLayerState::Changes::AffectsChildren; + if (clientChanges & layer_state_t::REQUIRES_COMPOSITION) + changes |= RequestedLayerState::Changes::RequiresComposition; if (clientChanges & layer_state_t::INPUT_CHANGES) changes |= RequestedLayerState::Changes::Input; if (clientChanges & layer_state_t::VISIBLE_REGION_CHANGES) diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 09f33de63b..4829d4c1e9 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -57,6 +57,7 @@ struct RequestedLayerState : layer_state_t { BufferSize = 1u << 18, GameMode = 1u << 19, BufferUsageFlags = 1u << 20, + RequiresComposition = 1u << 21, }; static Rect reduce(const Rect& win, const Region& exclude); RequestedLayerState(const LayerCreationArgs&); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 66385d8db5..f13b4ce9e8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1411,11 +1411,12 @@ void SurfaceFlinger::applyActiveMode(const sp& display) { } } -void SurfaceFlinger::initiateDisplayModeChanges() { +bool SurfaceFlinger::initiateDisplayModeChanges() { ATRACE_CALL(); std::optional displayToUpdateImmediately; + bool mustComposite = false; for (const auto& [id, physical] : mPhysicalDisplays) { const auto display = getDisplayDeviceLocked(id); if (!display) continue; @@ -1472,7 +1473,11 @@ void SurfaceFlinger::initiateDisplayModeChanges() { mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline); if (outTimeline.refreshRequired) { - scheduleComposite(FrameHint::kNone); + if (FlagManager::getInstance().vrr_bugfix_24q4()) { + mustComposite = true; + } else { + scheduleComposite(FrameHint::kNone); + } } else { // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange` // for all displays. This was only needed when the loop iterated over `mDisplays` rather @@ -1490,6 +1495,8 @@ void SurfaceFlinger::initiateDisplayModeChanges() { applyActiveMode(display); } } + + return mustComposite; } void SurfaceFlinger::disableExpensiveRendering() { @@ -2439,7 +2446,12 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, mUpdateAttachedChoreographer = true; } outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; - mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0; + if (FlagManager::getInstance().vrr_bugfix_24q4()) { + mustComposite |= mLayerLifecycleManager.getGlobalChanges().test( + frontend::RequestedLayerState::Changes::RequiresComposition); + } else { + mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0; + } bool newDataLatched = false; ATRACE_NAME("DisplayCallbackAndStatsUpdates"); @@ -2664,7 +2676,7 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, ? &mLayerHierarchyBuilder.getHierarchy() : nullptr, updateAttachedChoreographer); - initiateDisplayModeChanges(); + mustComposite |= initiateDisplayModeChanges(); } updateCursorAsync(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index edcc8d3206..a1987682c2 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -736,7 +736,7 @@ private: status_t setActiveModeFromBackdoor(const sp&, DisplayModeId, Fps minFps, Fps maxFps); - void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext); + bool initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext); void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext); // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler. diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 0feacacd80..121629fd10 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -134,6 +134,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(screenshot_fence_preservation); DUMP_READ_ONLY_FLAG(vulkan_renderengine); DUMP_READ_ONLY_FLAG(renderable_buffer_usage); + DUMP_READ_ONLY_FLAG(vrr_bugfix_24q4); DUMP_READ_ONLY_FLAG(restore_blur_step); DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro); DUMP_READ_ONLY_FLAG(protected_if_client); @@ -234,6 +235,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "") FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step") FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "") FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "") +FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_24q4, ""); FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, ""); FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite") FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, ""); diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 5850fe1b02..4cf4453980 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -72,6 +72,7 @@ public: bool enable_layer_command_batching() const; bool screenshot_fence_preservation() const; bool vulkan_renderengine() const; + bool vrr_bugfix_24q4() const; bool renderable_buffer_usage() const; bool restore_blur_step() const; bool dont_skip_on_early_ro() const; diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 04f1d5080a..5b94f07dff 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -84,4 +84,15 @@ flag { is_fixed_read_only: true } # local_tonemap_screenshots +flag { + name: "vrr_bugfix_24q4" + namespace: "core_graphics" + description: "bug fixes for VRR" + bug: "331513837" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # vrr_bugfix_24q4 + # IMPORTANT - please keep alphabetize to reduce merge conflicts diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index 867ff55632..158db752f4 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -461,8 +461,9 @@ TEST_F(LayerLifecycleManagerTest, layerOpacityChangesSetsVisibilityChangeFlag) { HAL_PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_PROTECTED /*usage*/)); EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(), - ftl::Flags(RequestedLayerState::Changes::Buffer | - RequestedLayerState::Changes::Content) + ftl::Flags( + RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content | + RequestedLayerState::Changes::RequiresComposition) .get()); mLifecycleManager.commitChanges(); @@ -493,10 +494,11 @@ TEST_F(LayerLifecycleManagerTest, bufferFormatChangesSetsVisibilityChangeFlag) { HAL_PIXEL_FORMAT_RGB_888, GRALLOC_USAGE_PROTECTED /*usage*/)); EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(), - ftl::Flags(RequestedLayerState::Changes::Buffer | - RequestedLayerState::Changes::Content | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::Visibility) + ftl::Flags( + RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::RequiresComposition) .get()); mLifecycleManager.commitChanges(); } @@ -520,7 +522,8 @@ TEST_F(LayerLifecycleManagerTest, roundedCornerChangesSetsVisibilityChangeFlag) RequestedLayerState::Changes::AffectsChildren | RequestedLayerState::Changes::Content | RequestedLayerState::Changes::Geometry | - RequestedLayerState::Changes::VisibleRegion) + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::RequiresComposition) .get()); mLifecycleManager.commitChanges(); } @@ -538,7 +541,8 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags( RequestedLayerState::Changes::Content | RequestedLayerState::Changes::AffectsChildren | - RequestedLayerState::Changes::VisibleRegion) + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::RequiresComposition) .string()); EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast(startingAlpha)); mLifecycleManager.commitChanges(); @@ -551,7 +555,8 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags( RequestedLayerState::Changes::Content | RequestedLayerState::Changes::AffectsChildren | - RequestedLayerState::Changes::VisibleRegion) + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::RequiresComposition) .string()); EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast(endingAlpha)); mLifecycleManager.commitChanges(); @@ -580,8 +585,9 @@ TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) { HAL_PIXEL_FORMAT_RGBA_8888, GRALLOC_USAGE_SW_READ_NEVER /*usage*/)); EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(), - ftl::Flags(RequestedLayerState::Changes::Buffer | - RequestedLayerState::Changes::Content) + ftl::Flags( + RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content | + RequestedLayerState::Changes::RequiresComposition) .get()); mLifecycleManager.commitChanges(); diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 5ff6417482..9dd38d60e7 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -254,7 +254,9 @@ TEST_F(LayerSnapshotTest, UpdateClearsPreviousChangeStates) { TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) { setColor(11, {1._hf, 0._hf, 0._hf}); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); - EXPECT_EQ(getSnapshot(11)->changes, RequestedLayerState::Changes::Content); + EXPECT_EQ(getSnapshot(11)->changes, + RequestedLayerState::Changes::Content | + RequestedLayerState::Changes::RequiresComposition); EXPECT_EQ(getSnapshot(11)->clientChanges, layer_state_t::eColorChanged); EXPECT_EQ(getSnapshot(1)->changes.get(), 0u); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); @@ -264,7 +266,9 @@ TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) { TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) { setColor(1, {1._hf, 0._hf, 0._hf}); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); - EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content); + EXPECT_EQ(getSnapshot(1)->changes, + RequestedLayerState::Changes::Content | + RequestedLayerState::Changes::RequiresComposition); EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eColorChanged); } -- GitLab From 7d1e9cebb3546a667fb24dc43d2b25568177d068 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 17 May 2024 14:51:24 -0700 Subject: [PATCH 356/465] SF: don't mark frame activity for NO_PREF category Bug: 339788845 Test: android.platform.test.scenario.sysui.quicksettings.QSHeaderAfterRotationTestMicrobenchmark#afterRotation_iconsVisible Change-Id: I85696c0c4316d4e2fd6f31348d7c73c3ae771b97 --- services/surfaceflinger/TransactionState.h | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h index 89a8f9238b..e5d648194f 100644 --- a/services/surfaceflinger/TransactionState.h +++ b/services/surfaceflinger/TransactionState.h @@ -23,6 +23,7 @@ #include "FrontEnd/LayerCreationArgs.h" #include "renderengine/ExternalTexture.h" +#include #include #include @@ -108,9 +109,22 @@ struct TransactionState { for (const auto& state : states) { const bool frameRateChanged = state.state.what & layer_state_t::eFrameRateChanged; - if (!frameRateChanged || - state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) { - return true; + if (FlagManager::getInstance().vrr_bugfix_24q4()) { + const bool frameRateIsNoVote = frameRateChanged && + state.state.frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_NO_VOTE; + const bool frameRateCategoryChanged = + state.state.what & layer_state_t::eFrameRateCategoryChanged; + const bool frameRateCategoryIsNoPreference = frameRateCategoryChanged && + state.state.frameRateCategory == + ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE; + if (!frameRateIsNoVote && !frameRateCategoryIsNoPreference) { + return true; + } + } else { + if (!frameRateChanged || + state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) { + return true; + } } } -- GitLab From 8eb8f35c8d45282e480426bc26543acf31230cb8 Mon Sep 17 00:00:00 2001 From: Garfield Tan Date: Fri, 17 May 2024 09:12:01 -0700 Subject: [PATCH 357/465] SF: Update metadata of unvisible layers to backend ARC++ relies on metadata to convey a variety of information, including window states through a in-house metadata. Some of them may change when a layer is invisible, and ARC++ backend needs to respond to these changes immediately. Such case may happen when user minimizes a window. Bug: 340946541 Test: Minimize on ARC++. Test: atest libsurfaceflinger_unittest Change-Id: Ie384cc8bd672a8277ef9437f908a6f969e422aee --- .../FrontEnd/LayerSnapshotBuilder.cpp | 40 +++++++++---- .../FrontEnd/LayerSnapshotBuilder.h | 5 ++ services/surfaceflinger/SurfaceFlinger.cpp | 10 +++- .../tests/unittests/LayerSnapshotTest.cpp | 60 +++++++++++++++++++ 4 files changed, 104 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index caeb575c4e..f10bb33a2f 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -313,6 +313,21 @@ void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requeste } } +void updateMetadataAndGameMode(LayerSnapshot& snapshot, const RequestedLayerState& requested, + const LayerSnapshotBuilder::Args& args, + const LayerSnapshot& parentSnapshot) { + if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { + snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) + ? requested.gameMode + : parentSnapshot.gameMode; + } + updateMetadata(snapshot, requested, args); + if (args.includeMetadata) { + snapshot.layerMetadata = parentSnapshot.layerMetadata; + snapshot.layerMetadata.merge(requested.metadata); + } +} + void clearChanges(LayerSnapshot& snapshot) { snapshot.changes.clear(); snapshot.clientChanges = 0; @@ -746,6 +761,11 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a RequestedLayerState::Changes::Input)) { updateInput(snapshot, requested, parentSnapshot, path, args); } + if (forceUpdate || + (args.includeMetadata && + snapshot.changes.test(RequestedLayerState::Changes::Metadata))) { + updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot); + } return; } @@ -786,16 +806,7 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) { - if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { - snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) - ? requested.gameMode - : parentSnapshot.gameMode; - } - updateMetadata(snapshot, requested, args); - if (args.includeMetadata) { - snapshot.layerMetadata = parentSnapshot.layerMetadata; - snapshot.layerMetadata.merge(requested.metadata); - } + updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot); } if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged || @@ -1164,6 +1175,15 @@ void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { } } +void LayerSnapshotBuilder::forEachSnapshot(const Visitor& visitor, + const ConstPredicate& predicate) { + for (int i = 0; i < mNumInterestingSnapshots; i++) { + std::unique_ptr& snapshot = mSnapshots.at((size_t)i); + if (!predicate(*snapshot)) continue; + visitor(snapshot); + } +} + void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const { for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) { LayerSnapshot& snapshot = *mSnapshots[(size_t)i]; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index 1cec0183b9..dbbad7664a 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -86,6 +86,11 @@ public: // Visit each visible snapshot in z-order and move the snapshot if needed void forEachVisibleSnapshot(const Visitor& visitor); + typedef std::function ConstPredicate; + // Visit each snapshot that satisfies the predicate and move the snapshot if needed with visible + // snapshots in z-order + void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate); + // Visit each snapshot interesting to input reverse z-order void forEachInputSnapshot(const ConstVisitor& visitor) const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 33dbab01df..f0c7ff0f08 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -9233,7 +9233,9 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit std::vector> layers; if (mLayerLifecycleManagerEnabled) { nsecs_t currentTime = systemTime(); - mLayerSnapshotBuilder.forEachVisibleSnapshot( + const bool needsMetadata = mCompositionEngine->getFeatureFlags().test( + compositionengine::Feature::kSnapshotLayerMetadata); + mLayerSnapshotBuilder.forEachSnapshot( [&](std::unique_ptr& snapshot) FTL_FAKE_GUARD( kMainThreadContext) { if (cursorOnly && @@ -9256,6 +9258,12 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit layerFE->mSnapshot = std::move(snapshot); refreshArgs.layers.push_back(layerFE); layers.emplace_back(legacyLayer.get(), layerFE.get()); + }, + [needsMetadata](const frontend::LayerSnapshot& snapshot) { + return snapshot.isVisible || + (needsMetadata && + snapshot.changes.test( + frontend::RequestedLayerState::Changes::Metadata)); }); } if (!mLayerLifecycleManagerEnabled) { diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 5ff6417482..0011c128b0 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -329,6 +329,55 @@ TEST_F(LayerSnapshotTest, UpdateMetadata) { EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789); } +TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) { + hideLayer(1); + + std::vector transactions; + transactions.emplace_back(); + transactions.back().states.push_back({}); + transactions.back().states.front().state.what = layer_state_t::eMetadataChanged; + // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly, + // and not using stale data. + transactions.back().states.front().state.metadata = LayerMetadata(); + transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123); + transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234); + transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345); + transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456); + transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567); + transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678); + transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789); + + transactions.back().states.front().layerId = 1; + transactions.back().states.front().state.layerId = static_cast(1); + + mLifecycleManager.applyTransactions(transactions); + EXPECT_EQ(mLifecycleManager.getGlobalChanges(), + RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::Visibility | + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::AffectsChildren); + + // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot + LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), + .layerLifecycleManager = mLifecycleManager, + .includeMetadata = true, + .displays = mFrontEndDisplayInfos, + .globalShadowSettings = globalShadowSettings, + .supportsBlur = true, + .supportedLayerGenericMetadata = {}, + .genericLayerMetadataKeyMap = {}}; + update(mSnapshotBuilder, args); + + EXPECT_EQ(static_cast(getSnapshot(1)->clientChanges), + layer_state_t::eMetadataChanged | layer_state_t::eFlagsChanged); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678); + EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789); +} + TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { // ROOT // ├── 1 @@ -1325,6 +1374,17 @@ TEST_F(LayerSnapshotTest, NonVisibleLayerWithInput) { EXPECT_TRUE(foundInputLayer); } +TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) { + std::vector visitedUniqueSequences; + mSnapshotBuilder.forEachSnapshot( + [&](const std::unique_ptr& snapshot) { + visitedUniqueSequences.push_back(snapshot->uniqueSequence); + }, + [](const frontend::LayerSnapshot& snapshot) { return snapshot.uniqueSequence == 111; }); + EXPECT_EQ(visitedUniqueSequences.size(), 1u); + EXPECT_EQ(visitedUniqueSequences[0], 111u); +} + TEST_F(LayerSnapshotTest, canOccludePresentation) { setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation); LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), -- GitLab From 377510847cd8350bb8e06925a12cf4557ec91f71 Mon Sep 17 00:00:00 2001 From: Linnan Li Date: Tue, 23 Apr 2024 12:20:14 +0800 Subject: [PATCH 358/465] Add enabled state to InputDevice and remove isInputDeviceEnabled(2/n) Since we have added a field for the enabled state to InputDeviceInfo, we have also added the same status field to InputDevice.java. This way, we won't need to call the isInputDeviceEnabled method in InputManager again to send a request to InputManagerService to check if the device is enabled. At this point, we can directly remove the isInputDeviceEnabled method in InputManager, which is used as a hidden API, since it is no longer in use. Bug: 336420877 Test: build & atest InputTests Change-Id: I7c2a600a55941dcb55c189c1e1f24e6787173d64 Signed-off-by: Linnan Li --- services/inputflinger/include/InputReaderBase.h | 3 --- services/inputflinger/reader/InputReader.cpp | 11 ----------- services/inputflinger/reader/include/InputReader.h | 2 -- .../inputflinger/tests/fuzzers/InputReaderFuzzer.cpp | 3 --- 4 files changed, 19 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 6cf5a7e1e5..f2a627035c 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -310,9 +310,6 @@ public: /* Called by the heartbeat to ensures that the reader has not deadlocked. */ virtual void monitor() = 0; - /* Returns true if the input device is enabled. */ - virtual bool isInputDeviceEnabled(int32_t deviceId) = 0; - /* Makes the reader start processing events from the kernel. */ virtual status_t start() = 0; diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index 69555f8961..b9523ef332 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -874,17 +874,6 @@ std::optional InputReader::getBluetoothAddress(int32_t deviceId) co return std::nullopt; } -bool InputReader::isInputDeviceEnabled(int32_t deviceId) { - std::scoped_lock _l(mLock); - - InputDevice* device = findInputDeviceLocked(deviceId); - if (device) { - return device->isEnabled(); - } - ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId); - return false; -} - bool InputReader::canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) { std::scoped_lock _l(mLock); diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 92a778a5bb..7e701c5341 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -61,8 +61,6 @@ public: std::vector getInputDevices() const override; - bool isInputDeviceEnabled(int32_t deviceId) override; - int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) override; int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) override; int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) override; diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp index a19726a479..7d26a43440 100644 --- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp @@ -55,8 +55,6 @@ public: void monitor() { reader->monitor(); } - bool isInputDeviceEnabled(int32_t deviceId) { return reader->isInputDeviceEnabled(deviceId); } - status_t start() { return reader->start(); } status_t stop() { return reader->stop(); } @@ -206,7 +204,6 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { reader->monitor(); }, [&]() -> void { reader->getInputDevices(); }, - [&]() -> void { reader->isInputDeviceEnabled(fdp->ConsumeIntegral()); }, [&]() -> void { reader->getScanCodeState(fdp->ConsumeIntegral(), fdp->ConsumeIntegral(), -- GitLab From 60b45628fc6c76a21801a828f1cb86ee19efc305 Mon Sep 17 00:00:00 2001 From: Mayank Garg Date: Sun, 5 May 2024 20:23:56 -0700 Subject: [PATCH 359/465] Rename the newly added maps variable to be more descriptive Bug: 338665345 Test: atest InputTests Change-Id: I3760407b2e9a67aed30ba92b20307c08a1c074d4 --- services/inputflinger/include/InputReaderBase.h | 6 +++--- services/inputflinger/reader/InputDevice.cpp | 8 ++++---- services/inputflinger/tests/FakeInputReaderPolicy.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h index 6cf5a7e1e5..795f088290 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -106,15 +106,15 @@ struct InputReaderConfiguration { // The associations between input ports and display ports. // Used to determine which DisplayViewport should be tied to which InputDevice. - std::unordered_map portAssociations; + std::unordered_map inputPortToDisplayPortAssociations; // The associations between input device ports and display unique ids. // Used to determine which DisplayViewport should be tied to which InputDevice. - std::unordered_map uniqueIdAssociationsByPort; + std::unordered_map inputPortToDisplayUniqueIdAssociations; // The associations between input device descriptor and display unique ids. // Used to determine which DisplayViewport should be tied to which InputDevice. - std::unordered_map uniqueIdAssociationsByDescriptor; + std::unordered_map inputDeviceDescriptorToDisplayUniqueIdAssociations; // The associations between input device ports device types. // This is used to determine which device type and source should be tied to which InputDevice. diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index 956484c931..b807b2714f 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -281,14 +281,14 @@ std::list InputDevice::configureInternal(nsecs_t when, const std::string& inputDeviceDescriptor = mIdentifier.descriptor; if (!inputDeviceDescriptor.empty()) { const std::unordered_map& ports = - readerConfig.portAssociations; + readerConfig.inputPortToDisplayPortAssociations; const auto& displayPort = ports.find(inputDeviceDescriptor); if (displayPort != ports.end()) { mAssociatedDisplayPort = std::make_optional(displayPort->second); } else { const std::unordered_map& displayUniqueIdsByDescriptor = - readerConfig.uniqueIdAssociationsByDescriptor; + readerConfig.inputDeviceDescriptorToDisplayUniqueIdAssociations; const auto& displayUniqueIdByDescriptor = displayUniqueIdsByDescriptor.find(inputDeviceDescriptor); if (displayUniqueIdByDescriptor != displayUniqueIdsByDescriptor.end()) { @@ -301,13 +301,13 @@ std::list InputDevice::configureInternal(nsecs_t when, const std::string& inputPort = mIdentifier.location; if (!inputPort.empty()) { const std::unordered_map& ports = - readerConfig.portAssociations; + readerConfig.inputPortToDisplayPortAssociations; const auto& displayPort = ports.find(inputPort); if (displayPort != ports.end()) { mAssociatedDisplayPort = std::make_optional(displayPort->second); } else { const std::unordered_map& displayUniqueIdsByPort = - readerConfig.uniqueIdAssociationsByPort; + readerConfig.inputPortToDisplayUniqueIdAssociations; const auto& displayUniqueIdByPort = displayUniqueIdsByPort.find(inputPort); if (displayUniqueIdByPort != displayUniqueIdsByPort.end()) { mAssociatedDisplayUniqueIdByPort = displayUniqueIdByPort->second; diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp index 088c7df046..d2cb0ac3df 100644 --- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp +++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp @@ -129,7 +129,7 @@ void FakeInputReaderPolicy::addExcludedDeviceName(const std::string& deviceName) void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) { - mConfig.portAssociations.insert({inputPort, displayPort}); + mConfig.inputPortToDisplayPortAssociations.insert({inputPort, displayPort}); } void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort, @@ -139,7 +139,7 @@ void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPor void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId, const std::string& displayUniqueId) { - mConfig.uniqueIdAssociationsByPort.insert({inputUniqueId, displayUniqueId}); + mConfig.inputPortToDisplayUniqueIdAssociations.insert({inputUniqueId, displayUniqueId}); } void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId, -- GitLab From e96e79fa96970e1cea5b8b45c781602fc963c8f9 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 20 May 2024 18:00:39 +0000 Subject: [PATCH 360/465] SF: use a pointer for out param in classifyJankLocked Due to a potential HWASAN bug (b/341741835) changing the out parameter semantics from reference to pointer. Bug: 340633280 Change-Id: I230d2073af69e3ec9312d8208d245cb5b743ec35 Test: presubmit --- .../FrameTimeline/FrameTimeline.cpp | 20 ++++++++++++------- .../FrameTimeline/FrameTimeline.h | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 8369a1ec64..c9ed15781d 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -543,12 +543,14 @@ std::string SurfaceFrame::miniDump() const { } void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate, - Fps displayFrameRenderRate, nsecs_t& deadlineDelta) { + Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) { if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) { // Cannot do any classification for invalid present time. mJankType = JankType::Unknown; mJankSeverityType = JankSeverityType::Unknown; - deadlineDelta = -1; + if (outDeadlineDelta) { + *outDeadlineDelta = -1; + } return; } @@ -559,7 +561,9 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r mJankType = mPresentState != PresentState::Presented ? JankType::Dropped : JankType::AppDeadlineMissed; mJankSeverityType = JankSeverityType::Unknown; - deadlineDelta = -1; + if (outDeadlineDelta) { + *outDeadlineDelta = -1; + } return; } @@ -568,11 +572,14 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r return; } - deadlineDelta = mActuals.endTime - mPredictions.endTime; const nsecs_t presentDelta = mActuals.presentTime - mPredictions.presentTime; const nsecs_t deltaToVsync = refreshRate.getPeriodNsecs() > 0 ? std::abs(presentDelta) % refreshRate.getPeriodNsecs() : 0; + const nsecs_t deadlineDelta = mActuals.endTime - mPredictions.endTime; + if (outDeadlineDelta) { + *outDeadlineDelta = deadlineDelta; + } if (deadlineDelta > mJankClassificationThresholds.deadlineThreshold) { mFrameReadyMetadata = FrameReadyMetadata::LateFinish; @@ -671,7 +678,7 @@ void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType, mActuals.presentTime = presentTime; nsecs_t deadlineDelta = 0; - classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, deadlineDelta); + classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, &deadlineDelta); if (mPredictionState != PredictionState::None) { // Only update janky frames if the app used vsync predictions @@ -686,8 +693,7 @@ void SurfaceFrame::onCommitNotComposited(Fps refreshRate, Fps displayFrameRender mDisplayFrameRenderRate = displayFrameRenderRate; mActuals.presentTime = mPredictions.presentTime; - nsecs_t deadlineDelta = 0; - classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, deadlineDelta); + classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, nullptr); } void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const { diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h index 21bc95a4c5..94cfcb40b9 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h @@ -237,7 +237,7 @@ private: void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const; void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const; void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate, - Fps displayFrameRenderRate, nsecs_t& deadlineDelta) REQUIRES(mMutex); + Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) REQUIRES(mMutex); const int64_t mToken; const int32_t mInputEventId; -- GitLab From 65beac9b0ad0d6c73ca5a7a367a30e1d33818c99 Mon Sep 17 00:00:00 2001 From: Rocky Fang Date: Mon, 20 May 2024 21:01:21 +0000 Subject: [PATCH 361/465] Release dynamic sensor data at the end of process This makes sure that sensor service has full control of the life time of the dynamic sensor data, and will not be affected by when the callback is invoked. Fixes: 329020894 Fixes: 337741176 Test: Connect a dynamic sensor to phone, rapidly turn on/off bluetooth to emulate fast connection/disconnection of dynamic sensor. Not seeing crash anymore Change-Id: I6c9b4fa06e08dc1bb0b5e578ee2ec10b95fe84c3 --- services/sensorservice/SensorDevice.cpp | 20 ++++++++++++-------- services/sensorservice/SensorDevice.h | 8 ++++++++ services/sensorservice/SensorService.cpp | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp index f62562ce9d..9c4d1ace15 100644 --- a/services/sensorservice/SensorDevice.cpp +++ b/services/sensorservice/SensorDevice.cpp @@ -429,14 +429,18 @@ void SensorDevice::onDynamicSensorsConnected(const std::vector& dynami } void SensorDevice::onDynamicSensorsDisconnected( - const std::vector& dynamicSensorHandlesRemoved) { - if (sensorservice_flags::sensor_device_on_dynamic_sensor_disconnected()) { - for (auto handle : dynamicSensorHandlesRemoved) { - auto it = mConnectedDynamicSensors.find(handle); - if (it != mConnectedDynamicSensors.end()) { - mConnectedDynamicSensors.erase(it); - } - } + const std::vector& /*dynamicSensorHandlesRemoved*/) { + // This function is currently a no-op has removing data in mConnectedDynamicSensors here will + // cause a race condition between when this callback is invoked and when the dynamic sensor meta + // event is processed by polling. The clean up should only happen after processing the meta + // event. See the call stack of cleanupDisconnectedDynamicSensor. +} + +void SensorDevice::cleanupDisconnectedDynamicSensor(int handle) { + std::lock_guard lock(mDynamicSensorsMutex); + auto it = mConnectedDynamicSensors.find(handle); + if (it != mConnectedDynamicSensors.end()) { + mConnectedDynamicSensors.erase(it); } } diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h index 52f7cf2de8..b7b04b5d00 100644 --- a/services/sensorservice/SensorDevice.h +++ b/services/sensorservice/SensorDevice.h @@ -63,6 +63,14 @@ public: std::vector getDynamicSensorHandles(); void handleDynamicSensorConnection(int handle, bool connected); + /** + * Removes handle from connected dynamic sensor list. Note that this method must be called after + * SensorService has done using sensor data. + * + * @param handle of the disconnected dynamic sensor. + */ + void cleanupDisconnectedDynamicSensor(int handle); + status_t initCheck() const; int getHalDeviceVersion() const; diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 69e430901a..70ca7025d4 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -1273,6 +1273,7 @@ bool SensorService::threadLoop() { } else { int handle = mSensorEventBuffer[i].dynamic_sensor_meta.handle; disconnectDynamicSensor(handle, activeConnections); + device.cleanupDisconnectedDynamicSensor(handle); } } } -- GitLab From dd56df195ea3f251ae28765b55a017d7b8f96f50 Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Mon, 20 May 2024 14:56:38 -0700 Subject: [PATCH 362/465] Add flag override for MultiDeviceSlipperyWindowTest This test currently assumes that the flag is enabled, and fails on platforms where that's not the case. Bug: 337200792 Test: atest --host inputflinger_tests Change-Id: Ia789128f40ec2ea809b46a47ca7947a0fbe5ac3a --- services/inputflinger/tests/InputDispatcher_test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 8ad235f3a3..8de28c680f 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -7814,6 +7814,7 @@ TEST_F(InputDispatcherTest, MultiDeviceSpyWindowSlipTest) { * If device B reports more ACTION_MOVE events, the middle window should receive remaining events. */ TEST_F(InputDispatcherTest, MultiDeviceSlipperyWindowTest) { + SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true); std::shared_ptr application = std::make_shared(); sp leftWindow = sp::make(application, mDispatcher, "Left window", -- GitLab From def4e4323d03e8ffd6517886fade38944b33f686 Mon Sep 17 00:00:00 2001 From: Kholoud Mohamed Date: Wed, 15 May 2024 12:47:46 +0000 Subject: [PATCH 363/465] Skip user consent for subsequent reports Bug: 340439309 Test: atest android.bugreport.cts_root.BugreportManagerTest Change-Id: I1e6cabe599a3f52b4877ac1026ed4401fa86e6db --- cmds/dumpstate/DumpstateService.cpp | 13 ++- cmds/dumpstate/DumpstateService.h | 3 +- .../binder/android/os/IDumpstate.aidl | 4 +- cmds/dumpstate/dumpstate.cpp | 83 +++++++++++-------- cmds/dumpstate/dumpstate.h | 9 +- cmds/dumpstate/tests/dumpstate_smoke_test.cpp | 8 +- cmds/dumpstate/tests/dumpstate_test.cpp | 16 ++-- 7 files changed, 79 insertions(+), 57 deletions(-) diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index ba0a38aad9..a8d12a1a91 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -39,6 +39,7 @@ struct DumpstateInfo { std::string calling_package; int32_t user_id = -1; bool keep_bugreport_on_retrieval = false; + bool skip_user_consent = false; }; static binder::Status exception(uint32_t code, const std::string& msg, @@ -62,7 +63,8 @@ static binder::Status exception(uint32_t code, const std::string& msg, [[noreturn]] static void* dumpstate_thread_retrieve(void* data) { std::unique_ptr ds_info(static_cast(data)); - ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package, ds_info->keep_bugreport_on_retrieval); + ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package, + ds_info->keep_bugreport_on_retrieval, ds_info->skip_user_consent); MYLOGD("Finished retrieving a bugreport. Exiting.\n"); exit(0); } @@ -116,7 +118,8 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, int bugreport_mode, int bugreport_flags, const sp& listener, - bool is_screenshot_requested) { + bool is_screenshot_requested, + bool skip_user_consent) { MYLOGI("startBugreport() with mode: %d\n", bugreport_mode); // Ensure there is only one bugreport in progress at a time. @@ -151,7 +154,7 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid, std::unique_ptr options = std::make_unique(); options->Initialize(static_cast(bugreport_mode), bugreport_flags, - bugreport_fd, screenshot_fd, is_screenshot_requested); + bugreport_fd, screenshot_fd, is_screenshot_requested, skip_user_consent); if (bugreport_fd.get() == -1 || (options->do_screenshot && screenshot_fd.get() == -1)) { MYLOGE("Invalid filedescriptor"); @@ -207,6 +210,7 @@ binder::Status DumpstateService::retrieveBugreport( android::base::unique_fd bugreport_fd, const std::string& bugreport_file, const bool keep_bugreport_on_retrieval, + const bool skip_user_consent, const sp& listener) { ds_ = &(Dumpstate::GetInstance()); @@ -216,6 +220,7 @@ binder::Status DumpstateService::retrieveBugreport( ds_info->calling_package = calling_package; ds_info->user_id = user_id; ds_info->keep_bugreport_on_retrieval = keep_bugreport_on_retrieval; + ds_info->skip_user_consent = skip_user_consent; ds_->listener_ = listener; std::unique_ptr options = std::make_unique(); // Use a /dev/null FD when initializing options since none is provided. @@ -223,7 +228,7 @@ binder::Status DumpstateService::retrieveBugreport( TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC))); options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT, - 0, bugreport_fd, devnull_fd, false); + 0, bugreport_fd, devnull_fd, false, skip_user_consent); if (bugreport_fd.get() == -1) { MYLOGE("Invalid filedescriptor"); diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h index 7b76c36380..c99f70eb1b 100644 --- a/cmds/dumpstate/DumpstateService.h +++ b/cmds/dumpstate/DumpstateService.h @@ -44,7 +44,7 @@ class DumpstateService : public BinderService, public BnDumpst android::base::unique_fd bugreport_fd, android::base::unique_fd screenshot_fd, int bugreport_mode, int bugreport_flags, const sp& listener, - bool is_screenshot_requested) override; + bool is_screenshot_requested, bool skip_user_consent) override; binder::Status retrieveBugreport(int32_t calling_uid, const std::string& calling_package, @@ -52,6 +52,7 @@ class DumpstateService : public BinderService, public BnDumpst android::base::unique_fd bugreport_fd, const std::string& bugreport_file, const bool keep_bugreport_on_retrieval, + const bool skip_user_consent, const sp& listener) override; diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl index 97c470e08c..3b8fde9f51 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl @@ -96,7 +96,8 @@ interface IDumpstate { void startBugreport(int callingUid, @utf8InCpp String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, int bugreportMode, int bugreportFlags, - IDumpstateListener listener, boolean isScreenshotRequested); + IDumpstateListener listener, boolean isScreenshotRequested, + boolean skipUserConsent); /** * Cancels the bugreport currently in progress. @@ -130,5 +131,6 @@ interface IDumpstate { FileDescriptor bugreportFd, @utf8InCpp String bugreportFile, boolean keepBugreportOnRetrieval, + boolean skipUserConsent, IDumpstateListener listener); } diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 6b9a0a0e7e..d5125f01d6 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -2902,9 +2902,11 @@ void Dumpstate::DumpOptions::Initialize(BugreportMode bugreport_mode, int bugreport_flags, const android::base::unique_fd& bugreport_fd_in, const android::base::unique_fd& screenshot_fd_in, - bool is_screenshot_requested) { + bool is_screenshot_requested, + bool skip_user_consent) { this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA; this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; + this->skip_user_consent = skip_user_consent; // Duplicate the fds because the passed in fds don't outlive the binder transaction. bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0)); screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0)); @@ -2992,46 +2994,52 @@ Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& call } Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package, - const bool keep_bugreport_on_retrieval) { + const bool keep_bugreport_on_retrieval, + const bool skip_user_consent) { Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package, - keep_bugreport_on_retrieval); + keep_bugreport_on_retrieval, + skip_user_consent); HandleRunStatus(status); return status; } Dumpstate::RunStatus Dumpstate::RetrieveInternal(int32_t calling_uid, const std::string& calling_package, - const bool keep_bugreport_on_retrieval) { - consent_callback_ = new ConsentCallback(); - const String16 incidentcompanion("incidentcompanion"); - sp ics( - defaultServiceManager()->checkService(incidentcompanion)); - android::String16 package(calling_package.c_str()); - if (ics != nullptr) { - MYLOGD("Checking user consent via incidentcompanion service\n"); - android::interface_cast(ics)->authorizeReport( - calling_uid, package, String16(), String16(), - 0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get()); - } else { - MYLOGD( - "Unable to check user consent; incidentcompanion service unavailable\n"); - return RunStatus::USER_CONSENT_TIMED_OUT; - } - UserConsentResult consent_result = consent_callback_->getResult(); - int timeout_ms = 30 * 1000; - while (consent_result == UserConsentResult::UNAVAILABLE && - consent_callback_->getElapsedTimeMs() < timeout_ms) { - sleep(1); - consent_result = consent_callback_->getResult(); - } - if (consent_result == UserConsentResult::DENIED) { - return RunStatus::USER_CONSENT_DENIED; - } - if (consent_result == UserConsentResult::UNAVAILABLE) { - MYLOGD("Canceling user consent request via incidentcompanion service\n"); - android::interface_cast(ics)->cancelAuthorization( - consent_callback_.get()); - return RunStatus::USER_CONSENT_TIMED_OUT; + const bool keep_bugreport_on_retrieval, + const bool skip_user_consent) { + if (!android::app::admin::flags::onboarding_consentless_bugreports() || !skip_user_consent) { + consent_callback_ = new ConsentCallback(); + const String16 incidentcompanion("incidentcompanion"); + sp ics( + defaultServiceManager()->checkService(incidentcompanion)); + android::String16 package(calling_package.c_str()); + if (ics != nullptr) { + MYLOGD("Checking user consent via incidentcompanion service\n"); + + android::interface_cast(ics)->authorizeReport( + calling_uid, package, String16(), String16(), + 0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get()); + } else { + MYLOGD( + "Unable to check user consent; incidentcompanion service unavailable\n"); + return RunStatus::USER_CONSENT_TIMED_OUT; + } + UserConsentResult consent_result = consent_callback_->getResult(); + int timeout_ms = 30 * 1000; + while (consent_result == UserConsentResult::UNAVAILABLE && + consent_callback_->getElapsedTimeMs() < timeout_ms) { + sleep(1); + consent_result = consent_callback_->getResult(); + } + if (consent_result == UserConsentResult::DENIED) { + return RunStatus::USER_CONSENT_DENIED; + } + if (consent_result == UserConsentResult::UNAVAILABLE) { + MYLOGD("Canceling user consent request via incidentcompanion service\n"); + android::interface_cast(ics)->cancelAuthorization( + consent_callback_.get()); + return RunStatus::USER_CONSENT_TIMED_OUT; + } } bool copy_succeeded = @@ -3510,7 +3518,9 @@ void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) { void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) { if (multiuser_get_app_id(calling_uid) == AID_SHELL || - !CalledByApi() || options_->is_consent_deferred) { + !CalledByApi() || options_->is_consent_deferred || + (android::app::admin::flags::onboarding_consentless_bugreports() && + options_->skip_user_consent)) { // No need to get consent for shell triggered dumpstates, or not // through bugreporting API (i.e. no fd to copy back), or when consent // is deferred. @@ -3596,7 +3606,8 @@ Dumpstate::RunStatus Dumpstate::CopyBugreportIfUserConsented(int32_t calling_uid // If the caller has asked to copy the bugreport over to their directory, we need explicit // user consent (unless the caller is Shell). UserConsentResult consent_result; - if (multiuser_get_app_id(calling_uid) == AID_SHELL) { + if (multiuser_get_app_id(calling_uid) == AID_SHELL || (options_->skip_user_consent + && android::app::admin::flags::onboarding_consentless_bugreports())) { consent_result = UserConsentResult::APPROVED; } else { consent_result = consent_callback_->getResult(); diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 46d949e303..fcb8cf3c07 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -364,7 +364,7 @@ class Dumpstate { * Initialize() dumpstate before calling this method. */ RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package, - const bool keep_bugreport_on_retrieval); + const bool keep_bugreport_on_retrieval, const bool skip_user_consent); @@ -412,6 +412,7 @@ class Dumpstate { bool do_screenshot = false; bool is_screenshot_copied = false; bool is_consent_deferred = false; + bool skip_user_consent = false; bool is_remote_mode = false; bool show_header_only = false; bool telephony_only = false; @@ -448,7 +449,8 @@ class Dumpstate { void Initialize(BugreportMode bugreport_mode, int bugreport_flags, const android::base::unique_fd& bugreport_fd, const android::base::unique_fd& screenshot_fd, - bool is_screenshot_requested); + bool is_screenshot_requested, + bool skip_user_consent); /* Returns true if the options set so far are consistent. */ bool ValidateOptions() const; @@ -564,7 +566,8 @@ class Dumpstate { private: RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package); RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package, - const bool keep_bugreport_on_retrieval); + const bool keep_bugreport_on_retrieval, + const bool skip_user_consent); RunStatus DumpstateDefaultAfterCritical(); RunStatus dumpstate(); diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp index ccf64fe54e..a29923a4c1 100644 --- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp @@ -507,7 +507,7 @@ TEST_F(DumpstateBinderTest, Baseline) { ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), std::move(screenshot_fd), Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener, - true); + true, false); // startBugreport is an async call. Verify binder call succeeded first, then wait till listener // gets expected callbacks. EXPECT_TRUE(status.isOk()); @@ -545,7 +545,7 @@ TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) { android::binder::Status status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), std::move(screenshot_fd), 2000, // invalid bugreport mode - flags, listener, false); + flags, listener, false, false); EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); // The service should have died, freeing itself up for a new invocation. @@ -579,7 +579,7 @@ TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) { ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd), std::move(screenshot_fd), Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1, - true); + true, false); EXPECT_TRUE(status.isOk()); // try to make another call to startBugreport. This should fail. @@ -587,7 +587,7 @@ TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) { status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2), std::move(screenshot_fd2), Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, - listener2, true); + listener2, true, false); EXPECT_FALSE(status.isOk()); WaitTillExecutionComplete(listener2.get()); EXPECT_EQ(listener2->getErrorCode(), diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp index 2afabed813..18c2f94432 100644 --- a/cmds/dumpstate/tests/dumpstate_test.cpp +++ b/cmds/dumpstate/tests/dumpstate_test.cpp @@ -239,7 +239,7 @@ TEST_F(DumpOptionsTest, InitializeAdbShellBugreport) { } TEST_F(DumpOptionsTest, InitializeFullBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true, false); EXPECT_TRUE(options_.do_screenshot); // Other options retain default values @@ -253,7 +253,7 @@ TEST_F(DumpOptionsTest, InitializeFullBugReport) { } TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true, false); EXPECT_TRUE(options_.do_progress_updates); EXPECT_TRUE(options_.do_screenshot); @@ -267,7 +267,7 @@ TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) { } TEST_F(DumpOptionsTest, InitializeRemoteBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false, false); EXPECT_TRUE(options_.is_remote_mode); EXPECT_FALSE(options_.do_vibrate); EXPECT_FALSE(options_.do_screenshot); @@ -281,7 +281,7 @@ TEST_F(DumpOptionsTest, InitializeRemoteBugReport) { } TEST_F(DumpOptionsTest, InitializeWearBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true, false); EXPECT_TRUE(options_.do_screenshot); EXPECT_TRUE(options_.do_progress_updates); @@ -296,7 +296,7 @@ TEST_F(DumpOptionsTest, InitializeWearBugReport) { } TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false, false); EXPECT_FALSE(options_.do_screenshot); EXPECT_TRUE(options_.telephony_only); EXPECT_TRUE(options_.do_progress_updates); @@ -311,7 +311,7 @@ TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) { } TEST_F(DumpOptionsTest, InitializeWifiBugReport) { - options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false); + options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false, false); EXPECT_FALSE(options_.do_screenshot); EXPECT_TRUE(options_.wifi_only); @@ -491,12 +491,12 @@ TEST_F(DumpOptionsTest, InitializeBugreportFlags) { int flags = Dumpstate::BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA | Dumpstate::BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT; options_.Initialize( - Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true); + Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true, false); EXPECT_TRUE(options_.is_consent_deferred); EXPECT_TRUE(options_.use_predumped_ui_data); options_.Initialize( - Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true); + Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true, false); EXPECT_FALSE(options_.is_consent_deferred); EXPECT_FALSE(options_.use_predumped_ui_data); } -- GitLab From 6846ade9aab98df8bb186f2d256349131e79945a Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 20 May 2024 21:58:27 +0000 Subject: [PATCH 364/465] SF: partially revert df59f4744c0672cc69dad72b230a757c1e4be116 Avoid a composition cycle when the FrameRate votes updates, but keep the old behavior for all other cases. Bug: 339759346 Change-Id: Ic0696ddeee6ed10f1e6efcc0dbe9589d638900cd Test: android.platform.test.scenario.gmail.OpenCloseComposeEmailMicrobenchmark#testOpenCloseComposeEmail --- libs/gui/include/gui/LayerState.h | 3 --- .../FrontEnd/LayerLifecycleManager.cpp | 18 ++++++------------ .../FrontEnd/RequestedLayerState.cpp | 8 ++------ .../FrontEnd/RequestedLayerState.h | 9 ++++++++- services/surfaceflinger/SurfaceFlinger.cpp | 4 ++-- .../unittests/LayerLifecycleManagerTest.cpp | 18 ++++++------------ .../tests/unittests/LayerSnapshotTest.cpp | 6 ++---- 7 files changed, 26 insertions(+), 40 deletions(-) diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index ebdf2326d6..ca7acf9be4 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -279,9 +279,6 @@ struct layer_state_t { layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged | layer_state_t::eLayerStackChanged; - // Changes requiring a composition pass. - static constexpr uint64_t REQUIRES_COMPOSITION = layer_state_t::CONTENT_DIRTY; - // Changes that affect the visible region on a display. static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES | layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged; diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 52f8bea4ba..4b0618e5aa 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -41,8 +41,7 @@ void LayerLifecycleManager::addLayers(std::vectorparentId == layer.id) { linkedLayer->parentId = UNASSIGNED_LAYER_ID; if (linkedLayer->canBeDestroyed()) { - linkedLayer->changes |= RequestedLayerState::Changes::Destroyed | - RequestedLayerState::Changes::RequiresComposition; + linkedLayer->changes |= RequestedLayerState::Changes::Destroyed; layersToBeDestroyed.emplace_back(linkedLayer->id); } } @@ -253,8 +249,7 @@ void LayerLifecycleManager::applyTransactions(const std::vectorchanges |= RequestedLayerState::Changes::Content; mChangedLayers.push_back(bgColorLayer); - mGlobalChanges |= RequestedLayerState::Changes::Content | - RequestedLayerState::Changes::RequiresComposition; + mGlobalChanges |= RequestedLayerState::Changes::Content; } } @@ -412,8 +407,7 @@ void LayerLifecycleManager::fixRelativeZLoop(uint32_t relativeRootId) { layer.relativeParentId = unlinkLayer(layer.relativeParentId, layer.id); layer.changes |= RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::RelativeParent; - mGlobalChanges |= RequestedLayerState::Changes::Hierarchy | - RequestedLayerState::Changes::RequiresComposition; + mGlobalChanges |= RequestedLayerState::Changes::Hierarchy; } // Some layers mirror the entire display stack. Since we don't have a single root layer per display diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index f5e5b0233e..5631facdae 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -58,8 +58,7 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) parentId(args.parentId), layerIdToMirror(args.layerIdToMirror) { layerId = static_cast(args.sequence); - changes |= RequestedLayerState::Changes::Created | - RequestedLayerState::Changes::RequiresComposition; + changes |= RequestedLayerState::Changes::Created; metadata.merge(args.metadata); changes |= RequestedLayerState::Changes::Metadata; handleAlive = true; @@ -249,8 +248,7 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta if (hadSomethingToDraw != hasSomethingToDraw()) { changes |= RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::RequiresComposition; + RequestedLayerState::Changes::VisibleRegion; } if (clientChanges & layer_state_t::HIERARCHY_CHANGES) changes |= RequestedLayerState::Changes::Hierarchy; @@ -260,8 +258,6 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta changes |= RequestedLayerState::Changes::Geometry; if (clientChanges & layer_state_t::AFFECTS_CHILDREN) changes |= RequestedLayerState::Changes::AffectsChildren; - if (clientChanges & layer_state_t::REQUIRES_COMPOSITION) - changes |= RequestedLayerState::Changes::RequiresComposition; if (clientChanges & layer_state_t::INPUT_CHANGES) changes |= RequestedLayerState::Changes::Input; if (clientChanges & layer_state_t::VISIBLE_REGION_CHANGES) diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h index 4829d4c1e9..48b9640486 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h @@ -26,6 +26,7 @@ #include "TransactionState.h" namespace android::surfaceflinger::frontend { +using namespace ftl::flag_operators; // Stores client requested states for a layer. // This struct does not store any other states or states pertaining to @@ -57,8 +58,14 @@ struct RequestedLayerState : layer_state_t { BufferSize = 1u << 18, GameMode = 1u << 19, BufferUsageFlags = 1u << 20, - RequiresComposition = 1u << 21, }; + + static constexpr ftl::Flags kMustComposite = Changes::Created | Changes::Destroyed | + Changes::Hierarchy | Changes::Geometry | Changes::Content | Changes::Input | + Changes::Z | Changes::Mirror | Changes::Parent | Changes::RelativeParent | + Changes::Metadata | Changes::Visibility | Changes::VisibleRegion | Changes::Buffer | + Changes::SidebandStream | Changes::Animation | Changes::BufferSize | Changes::GameMode | + Changes::BufferUsageFlags; static Rect reduce(const Rect& win, const Region& exclude); RequestedLayerState(const LayerCreationArgs&); void merge(const ResolvedComposerState&); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 28d80188ff..e2af5cff8b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2447,8 +2447,8 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, } outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0; if (FlagManager::getInstance().vrr_bugfix_24q4()) { - mustComposite |= mLayerLifecycleManager.getGlobalChanges().test( - frontend::RequestedLayerState::Changes::RequiresComposition); + mustComposite |= mLayerLifecycleManager.getGlobalChanges().any( + frontend::RequestedLayerState::kMustComposite); } else { mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0; } diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index 158db752f4..cfc8e99f49 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -462,8 +462,7 @@ TEST_F(LayerLifecycleManagerTest, layerOpacityChangesSetsVisibilityChangeFlag) { GRALLOC_USAGE_PROTECTED /*usage*/)); EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(), ftl::Flags( - RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content | - RequestedLayerState::Changes::RequiresComposition) + RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content) .get()); mLifecycleManager.commitChanges(); @@ -497,8 +496,7 @@ TEST_F(LayerLifecycleManagerTest, bufferFormatChangesSetsVisibilityChangeFlag) { ftl::Flags( RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content | RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::RequiresComposition) + RequestedLayerState::Changes::Visibility) .get()); mLifecycleManager.commitChanges(); } @@ -522,8 +520,7 @@ TEST_F(LayerLifecycleManagerTest, roundedCornerChangesSetsVisibilityChangeFlag) RequestedLayerState::Changes::AffectsChildren | RequestedLayerState::Changes::Content | RequestedLayerState::Changes::Geometry | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::RequiresComposition) + RequestedLayerState::Changes::VisibleRegion) .get()); mLifecycleManager.commitChanges(); } @@ -541,8 +538,7 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags( RequestedLayerState::Changes::Content | RequestedLayerState::Changes::AffectsChildren | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::RequiresComposition) + RequestedLayerState::Changes::VisibleRegion) .string()); EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast(startingAlpha)); mLifecycleManager.commitChanges(); @@ -555,8 +551,7 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags( RequestedLayerState::Changes::Content | RequestedLayerState::Changes::AffectsChildren | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::RequiresComposition) + RequestedLayerState::Changes::VisibleRegion) .string()); EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast(endingAlpha)); mLifecycleManager.commitChanges(); @@ -586,8 +581,7 @@ TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) { GRALLOC_USAGE_SW_READ_NEVER /*usage*/)); EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(), ftl::Flags( - RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content | - RequestedLayerState::Changes::RequiresComposition) + RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content) .get()); mLifecycleManager.commitChanges(); diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index ce4d798dd8..23b6851bed 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -255,8 +255,7 @@ TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) { setColor(11, {1._hf, 0._hf, 0._hf}); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); EXPECT_EQ(getSnapshot(11)->changes, - RequestedLayerState::Changes::Content | - RequestedLayerState::Changes::RequiresComposition); + RequestedLayerState::Changes::Content); EXPECT_EQ(getSnapshot(11)->clientChanges, layer_state_t::eColorChanged); EXPECT_EQ(getSnapshot(1)->changes.get(), 0u); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); @@ -267,8 +266,7 @@ TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) { setColor(1, {1._hf, 0._hf, 0._hf}); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); EXPECT_EQ(getSnapshot(1)->changes, - RequestedLayerState::Changes::Content | - RequestedLayerState::Changes::RequiresComposition); + RequestedLayerState::Changes::Content); EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eColorChanged); } -- GitLab From 2269a699fd10485c45411d83ca4bfeeb7d64ef03 Mon Sep 17 00:00:00 2001 From: Sally Qi Date: Fri, 17 May 2024 18:02:28 -0700 Subject: [PATCH 365/465] Fix the logic of adding skipped frame. Bug: n/a Test: FrameTimelineTest Change-Id: I21d51d76229701f1fe66982488b5eefedeb02c0c --- .../FrameTimeline/FrameTimeline.cpp | 2 +- .../tests/unittests/FrameTimelineTest.cpp | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp index 8369a1ec64..c8e1fbb5e7 100644 --- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp +++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp @@ -1166,7 +1166,7 @@ void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs (static_cast(mSurfaceFlingerPredictions.presentTime) - kThresh * static_cast(mRenderRate.getPeriodNsecs())) && static_cast(surfaceFrame->getPredictions().presentTime) >= - (static_cast(previousPredictionPresentTime) - + (static_cast(previousPredictionPresentTime) + kThresh * static_cast(mRenderRate.getPeriodNsecs())) && // sf skipped frame is not considered if app is self janked !surfaceFrame->isSelfJanky()) { diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp index 9fd687c6e7..dac9265b71 100644 --- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp +++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp @@ -1132,6 +1132,72 @@ void validateTraceEvent(const ProtoFrameEnd& received, const ProtoFrameEnd& sour EXPECT_EQ(received.cookie(), source.cookie()); } +TEST_F(FrameTimelineTest, traceDisplayFrameNoSkipped) { + // setup 2 display frames + // DF 1: [22, 30] -> [0, 11] + // DF 2: [82, 90] -> SF [5, 16] + auto tracingSession = getTracingSessionForTest(); + tracingSession->StartBlocking(); + int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40}); + int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 100}); + int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({0, 11, 25}); + int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({5, 16, 30}); + auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + + int64_t traceCookie = snoopCurrentTraceCookie(); + + // set up 1st display frame + FrameTimelineInfo ftInfo1; + ftInfo1.vsyncId = surfaceFrameToken1; + ftInfo1.inputEventId = sInputEventId; + auto surfaceFrame1 = + mFrameTimeline->createSurfaceFrameForToken(ftInfo1, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); + surfaceFrame1->setAcquireFenceTime(11); + mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_30); + surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented); + mFrameTimeline->addSurfaceFrame(surfaceFrame1); + mFrameTimeline->setSfPresent(30, presentFence1); + presentFence1->signalForTest(40); + + // Trigger a flush by finalizing the next DisplayFrame + auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE); + FrameTimelineInfo ftInfo2; + ftInfo2.vsyncId = surfaceFrameToken2; + ftInfo2.inputEventId = sInputEventId; + auto surfaceFrame2 = + mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne, + sLayerNameOne, sLayerNameOne, + /*isBuffer*/ true, sGameMode); + + // set up 2nd display frame + surfaceFrame2->setAcquireFenceTime(16); + mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_11, RR_30); + surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented); + mFrameTimeline->addSurfaceFrame(surfaceFrame2); + mFrameTimeline->setSfPresent(90, presentFence2); + presentFence2->signalForTest(100); + + // the token of skipped Display Frame + auto protoSkippedActualDisplayFrameStart = + createProtoActualDisplayFrameStart(traceCookie + 9, 0, kSurfaceFlingerPid, + FrameTimelineEvent::PRESENT_DROPPED, true, false, + FrameTimelineEvent::JANK_DROPPED, + FrameTimelineEvent::SEVERITY_NONE, + FrameTimelineEvent::PREDICTION_VALID); + auto protoSkippedActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 9); + + // Trigger a flush by finalizing the next DisplayFrame + addEmptyDisplayFrame(); + flushTrace(); + tracingSession->StopBlocking(); + + auto packets = readFrameTimelinePacketsBlocking(tracingSession.get()); + // 8 Valid Display Frames + 8 Valid Surface Frames + no Skipped Display Frames + EXPECT_EQ(packets.size(), 16u); +} + TEST_F(FrameTimelineTest, traceDisplayFrameSkipped) { SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace, true); -- GitLab From ad34a68198c65bea568bac779fd8ad2937ba8087 Mon Sep 17 00:00:00 2001 From: Garfield Tan Date: Tue, 21 May 2024 15:25:35 +0000 Subject: [PATCH 366/465] Revert "SF: Update metadata of unvisible layers to backend" This reverts commit 8eb8f35c8d45282e480426bc26543acf31230cb8. Reason for revert: b/341389467#comment3 Change-Id: I318d6b3d324be0bfa400e2759b22f1bdbc91e9ed --- .../FrontEnd/LayerSnapshotBuilder.cpp | 40 ++++--------- .../FrontEnd/LayerSnapshotBuilder.h | 5 -- services/surfaceflinger/SurfaceFlinger.cpp | 10 +--- .../tests/unittests/LayerSnapshotTest.cpp | 60 ------------------- 4 files changed, 11 insertions(+), 104 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index f10bb33a2f..caeb575c4e 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -313,21 +313,6 @@ void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requeste } } -void updateMetadataAndGameMode(LayerSnapshot& snapshot, const RequestedLayerState& requested, - const LayerSnapshotBuilder::Args& args, - const LayerSnapshot& parentSnapshot) { - if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { - snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) - ? requested.gameMode - : parentSnapshot.gameMode; - } - updateMetadata(snapshot, requested, args); - if (args.includeMetadata) { - snapshot.layerMetadata = parentSnapshot.layerMetadata; - snapshot.layerMetadata.merge(requested.metadata); - } -} - void clearChanges(LayerSnapshot& snapshot) { snapshot.changes.clear(); snapshot.clientChanges = 0; @@ -761,11 +746,6 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a RequestedLayerState::Changes::Input)) { updateInput(snapshot, requested, parentSnapshot, path, args); } - if (forceUpdate || - (args.includeMetadata && - snapshot.changes.test(RequestedLayerState::Changes::Metadata))) { - updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot); - } return; } @@ -806,7 +786,16 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) { - updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot); + if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { + snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) + ? requested.gameMode + : parentSnapshot.gameMode; + } + updateMetadata(snapshot, requested, args); + if (args.includeMetadata) { + snapshot.layerMetadata = parentSnapshot.layerMetadata; + snapshot.layerMetadata.merge(requested.metadata); + } } if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged || @@ -1175,15 +1164,6 @@ void LayerSnapshotBuilder::forEachVisibleSnapshot(const Visitor& visitor) { } } -void LayerSnapshotBuilder::forEachSnapshot(const Visitor& visitor, - const ConstPredicate& predicate) { - for (int i = 0; i < mNumInterestingSnapshots; i++) { - std::unique_ptr& snapshot = mSnapshots.at((size_t)i); - if (!predicate(*snapshot)) continue; - visitor(snapshot); - } -} - void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const { for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) { LayerSnapshot& snapshot = *mSnapshots[(size_t)i]; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h index dbbad7664a..1cec0183b9 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h @@ -86,11 +86,6 @@ public: // Visit each visible snapshot in z-order and move the snapshot if needed void forEachVisibleSnapshot(const Visitor& visitor); - typedef std::function ConstPredicate; - // Visit each snapshot that satisfies the predicate and move the snapshot if needed with visible - // snapshots in z-order - void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate); - // Visit each snapshot interesting to input reverse z-order void forEachInputSnapshot(const ConstVisitor& visitor) const; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f0c7ff0f08..33dbab01df 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -9233,9 +9233,7 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit std::vector> layers; if (mLayerLifecycleManagerEnabled) { nsecs_t currentTime = systemTime(); - const bool needsMetadata = mCompositionEngine->getFeatureFlags().test( - compositionengine::Feature::kSnapshotLayerMetadata); - mLayerSnapshotBuilder.forEachSnapshot( + mLayerSnapshotBuilder.forEachVisibleSnapshot( [&](std::unique_ptr& snapshot) FTL_FAKE_GUARD( kMainThreadContext) { if (cursorOnly && @@ -9258,12 +9256,6 @@ std::vector> SurfaceFlinger::moveSnapshotsToComposit layerFE->mSnapshot = std::move(snapshot); refreshArgs.layers.push_back(layerFE); layers.emplace_back(legacyLayer.get(), layerFE.get()); - }, - [needsMetadata](const frontend::LayerSnapshot& snapshot) { - return snapshot.isVisible || - (needsMetadata && - snapshot.changes.test( - frontend::RequestedLayerState::Changes::Metadata)); }); } if (!mLayerLifecycleManagerEnabled) { diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 0011c128b0..5ff6417482 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -329,55 +329,6 @@ TEST_F(LayerSnapshotTest, UpdateMetadata) { EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789); } -TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) { - hideLayer(1); - - std::vector transactions; - transactions.emplace_back(); - transactions.back().states.push_back({}); - transactions.back().states.front().state.what = layer_state_t::eMetadataChanged; - // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly, - // and not using stale data. - transactions.back().states.front().state.metadata = LayerMetadata(); - transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123); - transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234); - transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345); - transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456); - transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567); - transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678); - transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789); - - transactions.back().states.front().layerId = 1; - transactions.back().states.front().state.layerId = static_cast(1); - - mLifecycleManager.applyTransactions(transactions); - EXPECT_EQ(mLifecycleManager.getGlobalChanges(), - RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::Visibility | - RequestedLayerState::Changes::VisibleRegion | - RequestedLayerState::Changes::AffectsChildren); - - // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot - LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), - .layerLifecycleManager = mLifecycleManager, - .includeMetadata = true, - .displays = mFrontEndDisplayInfos, - .globalShadowSettings = globalShadowSettings, - .supportsBlur = true, - .supportedLayerGenericMetadata = {}, - .genericLayerMetadataKeyMap = {}}; - update(mSnapshotBuilder, args); - - EXPECT_EQ(static_cast(getSnapshot(1)->clientChanges), - layer_state_t::eMetadataChanged | layer_state_t::eFlagsChanged); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789); -} - TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { // ROOT // ├── 1 @@ -1374,17 +1325,6 @@ TEST_F(LayerSnapshotTest, NonVisibleLayerWithInput) { EXPECT_TRUE(foundInputLayer); } -TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) { - std::vector visitedUniqueSequences; - mSnapshotBuilder.forEachSnapshot( - [&](const std::unique_ptr& snapshot) { - visitedUniqueSequences.push_back(snapshot->uniqueSequence); - }, - [](const frontend::LayerSnapshot& snapshot) { return snapshot.uniqueSequence == 111; }); - EXPECT_EQ(visitedUniqueSequences.size(), 1u); - EXPECT_EQ(visitedUniqueSequences[0], 111u); -} - TEST_F(LayerSnapshotTest, canOccludePresentation) { setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation); LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), -- GitLab From 10fbacda6dc007baca753d19ccfd148cd8a8eab9 Mon Sep 17 00:00:00 2001 From: Nergi Rahardi Date: Tue, 21 May 2024 09:37:59 +0000 Subject: [PATCH 367/465] Revert "Set Change::Metadata on LayerMetadata update" This reverts commit 6431d4afddc338a3e8a997f341df5a23d54efbc7. Reason for revert: b/341389467 Change-Id: Ia4bfd1a1be6bf9111ef38bd48d874b0bc3b42aa1 --- libs/gui/include/gui/LayerMetadata.h | 1 - .../FrontEnd/LayerSnapshotBuilder.cpp | 10 ++-- .../FrontEnd/RequestedLayerState.cpp | 1 - .../tests/unittests/LayerSnapshotTest.cpp | 46 +------------------ 4 files changed, 5 insertions(+), 53 deletions(-) diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h index 7ee291df4c..9cf62bc7d6 100644 --- a/libs/gui/include/gui/LayerMetadata.h +++ b/libs/gui/include/gui/LayerMetadata.h @@ -74,7 +74,6 @@ enum class GameMode : int32_t { } // namespace android::gui using android::gui::METADATA_ACCESSIBILITY_ID; -using android::gui::METADATA_CALLING_UID; using android::gui::METADATA_DEQUEUE_TIME; using android::gui::METADATA_GAME_MODE; using android::gui::METADATA_MOUSE_CURSOR; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index caeb575c4e..f2497d4bc8 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -785,12 +785,10 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } } - if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::Metadata)) { - if (snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { - snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) - ? requested.gameMode - : parentSnapshot.gameMode; - } + if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) { + snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) + ? requested.gameMode + : parentSnapshot.gameMode; updateMetadata(snapshot, requested, args); if (args.includeMetadata) { snapshot.layerMetadata = parentSnapshot.layerMetadata; diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 5631facdae..028bd19a60 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -328,7 +328,6 @@ void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerSta changes |= RequestedLayerState::Changes::GameMode; } } - changes |= RequestedLayerState::Changes::Metadata; } if (clientState.what & layer_state_t::eFrameRateChanged) { const auto compatibility = diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index 5ff6417482..82adadc368 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -278,57 +278,13 @@ TEST_F(LayerSnapshotTest, GameMode) { transactions.back().states.front().layerId = 1; transactions.back().states.front().state.layerId = static_cast(1); mLifecycleManager.applyTransactions(transactions); - EXPECT_EQ(mLifecycleManager.getGlobalChanges(), - RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata); + EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode); UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged); EXPECT_EQ(static_cast(getSnapshot(1)->gameMode), 42); EXPECT_EQ(static_cast(getSnapshot(11)->gameMode), 42); } -TEST_F(LayerSnapshotTest, UpdateMetadata) { - std::vector transactions; - transactions.emplace_back(); - transactions.back().states.push_back({}); - transactions.back().states.front().state.what = layer_state_t::eMetadataChanged; - // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly, - // and not using stale data. - transactions.back().states.front().state.metadata = LayerMetadata(); - transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123); - transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234); - transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345); - transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456); - transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567); - transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678); - transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789); - - transactions.back().states.front().layerId = 1; - transactions.back().states.front().state.layerId = static_cast(1); - - mLifecycleManager.applyTransactions(transactions); - EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Metadata); - - // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot - LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(), - .layerLifecycleManager = mLifecycleManager, - .includeMetadata = true, - .displays = mFrontEndDisplayInfos, - .globalShadowSettings = globalShadowSettings, - .supportsBlur = true, - .supportedLayerGenericMetadata = {}, - .genericLayerMetadataKeyMap = {}}; - update(mSnapshotBuilder, args); - - EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678); - EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789); -} - TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) { // ROOT // ├── 1 -- GitLab From c72e98692751682216fe03e68c0a0eb42678c9a1 Mon Sep 17 00:00:00 2001 From: Rocky Fang Date: Tue, 21 May 2024 18:49:32 +0000 Subject: [PATCH 368/465] Create flag for cleaning up dynamic sensor data Test: N/A Bug: 329020894 Change-Id: I1f5f9c667390b0c744cf7d57b9cd4dffebce8537 --- services/sensorservice/senserservice_flags.aconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig index f20b213823..5b499a8fd8 100644 --- a/services/sensorservice/senserservice_flags.aconfig +++ b/services/sensorservice/senserservice_flags.aconfig @@ -21,3 +21,10 @@ flag { description: "This flag controls we allow to pass in nullptr as scratch in SensorEventConnection::sendEvents()" bug: "339306599" } + +flag { + name: "sensor_service_clear_dynamic_sensor_data_at_the_end" + namespace: "sensors" + description: "When this flag is enabled, sensor service will only erase dynamic sensor data at the end of the threadLoop to prevent race condition." + bug: "329020894" +} \ No newline at end of file -- GitLab From 8445d195f0b9503568cbe249e5ba98cd6e0a3be0 Mon Sep 17 00:00:00 2001 From: Mayank Garg Date: Tue, 21 May 2024 14:31:14 -0700 Subject: [PATCH 369/465] Removed dead code from TouchInputMapper The view port resolution is already done in Input Device, there is no need to check for mapping again. Bug: 338665345 Test: atest InputTests Change-Id: I537b5828ea174eba601bb07f1983cf006baf6bfc --- .../inputflinger/reader/mapper/TouchInputMapper.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 8b4b691a45..62d7841ab8 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -537,18 +537,6 @@ std::optional TouchInputMapper::findViewport() { return getDeviceContext().getAssociatedViewport(); } - const std::optional associatedDisplayUniqueIdByDescriptor = - getDeviceContext().getAssociatedDisplayUniqueIdByDescriptor(); - if (associatedDisplayUniqueIdByDescriptor) { - return getDeviceContext().getAssociatedViewport(); - } - - const std::optional associatedDisplayUniqueIdByPort = - getDeviceContext().getAssociatedDisplayUniqueIdByPort(); - if (associatedDisplayUniqueIdByPort) { - return getDeviceContext().getAssociatedViewport(); - } - if (mDeviceMode == DeviceMode::POINTER) { std::optional viewport = mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId); -- GitLab From 78c90af2224b43467d854e356492527db0f8eb70 Mon Sep 17 00:00:00 2001 From: Alan Ding Date: Fri, 17 May 2024 16:55:51 -0700 Subject: [PATCH 370/465] ui: Refactor stable ID generation for GPU virtual displays This is a partial port of http://ag/17832464 for main. Use ftl::stable_hash to generate stable ID from uniqueId for GPU virtual displays. This allows FLAG_STABLE consistent with PhysicalDisplayId that uses a unique EDID, as well as being stable across reboots when using hardcoded or static unique ID. Add DisplayId.isStable()/isVirtual() interfaces and refactor previous legacy usages. Bug: 339525838 Bug: 137375833 Bug: 194863377 Test: atest libsurfaceflinger_unittest Test: atest DisplayId_test Flag: EXEMPT refactor Change-Id: I54f4d3803c8c23266a3461660146af7ae017e4be --- libs/ui/include/ui/DisplayId.h | 42 ++++++++++++------- libs/ui/tests/DisplayId_test.cpp | 23 ++++++++-- .../CompositionEngine/src/Display.cpp | 2 +- services/surfaceflinger/DisplayDevice.h | 2 +- .../DisplayHardware/VirtualDisplaySurface.cpp | 4 +- 5 files changed, 50 insertions(+), 23 deletions(-) diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h index 3a31fa0848..8a14db8ffa 100644 --- a/libs/ui/include/ui/DisplayId.h +++ b/libs/ui/include/ui/DisplayId.h @@ -20,6 +20,7 @@ #include #include +#include #include namespace android { @@ -38,6 +39,9 @@ struct DisplayId { constexpr DisplayId(const DisplayId&) = default; DisplayId& operator=(const DisplayId&) = default; + constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; } + constexpr bool isStable() const { return value & FLAG_STABLE; } + uint64_t value; // For deserialization. @@ -76,10 +80,10 @@ inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) { // DisplayId of a physical display, such as the internal display or externally connected display. struct PhysicalDisplayId : DisplayId { static constexpr ftl::Optional tryCast(DisplayId id) { - if (id.value & FLAG_VIRTUAL) { + if (id.isVirtual()) { return std::nullopt; } - return {PhysicalDisplayId(id)}; + return PhysicalDisplayId(id); } // Returns a stable ID based on EDID information. @@ -117,25 +121,23 @@ struct VirtualDisplayId : DisplayId { static constexpr uint64_t FLAG_GPU = 1ULL << 61; static constexpr std::optional tryCast(DisplayId id) { - if (id.value & FLAG_VIRTUAL) { - return {VirtualDisplayId(id)}; + if (id.isVirtual()) { + return VirtualDisplayId(id); } return std::nullopt; } protected: - constexpr VirtualDisplayId(uint64_t flags, BaseId baseId) - : DisplayId(DisplayId::FLAG_VIRTUAL | flags | baseId) {} - + explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {} explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {} }; struct HalVirtualDisplayId : VirtualDisplayId { - explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(0, baseId) {} + explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(baseId) {} static constexpr std::optional tryCast(DisplayId id) { - if ((id.value & FLAG_VIRTUAL) && !(id.value & VirtualDisplayId::FLAG_GPU)) { - return {HalVirtualDisplayId(id)}; + if (id.isVirtual() && !(id.value & FLAG_GPU)) { + return HalVirtualDisplayId(id); } return std::nullopt; } @@ -145,17 +147,27 @@ private: }; struct GpuVirtualDisplayId : VirtualDisplayId { - explicit constexpr GpuVirtualDisplayId(BaseId baseId) - : VirtualDisplayId(VirtualDisplayId::FLAG_GPU, baseId) {} + explicit constexpr GpuVirtualDisplayId(BaseId baseId) : VirtualDisplayId(FLAG_GPU | baseId) {} + + static constexpr std::optional fromUniqueId(const std::string& uniqueId) { + if (const auto hashOpt = ftl::stable_hash(uniqueId)) { + return GpuVirtualDisplayId(HashTag{}, *hashOpt); + } + return {}; + } static constexpr std::optional tryCast(DisplayId id) { - if ((id.value & FLAG_VIRTUAL) && (id.value & VirtualDisplayId::FLAG_GPU)) { - return {GpuVirtualDisplayId(id)}; + if (id.isVirtual() && (id.value & FLAG_GPU)) { + return GpuVirtualDisplayId(id); } return std::nullopt; } private: + struct HashTag {}; // Disambiguate with BaseId constructor. + constexpr GpuVirtualDisplayId(HashTag, uint64_t hash) + : VirtualDisplayId(FLAG_STABLE | FLAG_GPU | hash) {} + explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {} }; @@ -169,7 +181,7 @@ struct HalDisplayId : DisplayId { if (GpuVirtualDisplayId::tryCast(id)) { return std::nullopt; } - return {HalDisplayId(id)}; + return HalDisplayId(id); } private: diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp index 8ddee7e740..1115804954 100644 --- a/libs/ui/tests/DisplayId_test.cpp +++ b/libs/ui/tests/DisplayId_test.cpp @@ -24,7 +24,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) { constexpr uint8_t port = 1; constexpr uint16_t manufacturerId = 13; constexpr uint32_t modelHash = 42; - PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash); + const PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash); EXPECT_EQ(port, id.getPort()); EXPECT_EQ(manufacturerId, id.getManufacturerId()); EXPECT_FALSE(VirtualDisplayId::tryCast(id)); @@ -39,7 +39,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) { TEST(DisplayIdTest, createPhysicalIdFromPort) { constexpr uint8_t port = 3; - PhysicalDisplayId id = PhysicalDisplayId::fromPort(port); + const PhysicalDisplayId id = PhysicalDisplayId::fromPort(port); EXPECT_EQ(port, id.getPort()); EXPECT_FALSE(VirtualDisplayId::tryCast(id)); EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); @@ -52,7 +52,22 @@ TEST(DisplayIdTest, createPhysicalIdFromPort) { } TEST(DisplayIdTest, createGpuVirtualId) { - GpuVirtualDisplayId id(42); + const GpuVirtualDisplayId id(42); + EXPECT_TRUE(VirtualDisplayId::tryCast(id)); + EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(PhysicalDisplayId::tryCast(id)); + EXPECT_FALSE(HalDisplayId::tryCast(id)); + + EXPECT_EQ(id, DisplayId::fromValue(id.value)); + EXPECT_EQ(id, DisplayId::fromValue(id.value)); +} + +TEST(DisplayIdTest, createGpuVirtualIdFromUniqueId) { + static const std::string kUniqueId("virtual:ui:DisplayId_test"); + const auto idOpt = GpuVirtualDisplayId::fromUniqueId(kUniqueId); + ASSERT_TRUE(idOpt.has_value()); + const GpuVirtualDisplayId id = idOpt.value(); EXPECT_TRUE(VirtualDisplayId::tryCast(id)); EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); @@ -64,7 +79,7 @@ TEST(DisplayIdTest, createGpuVirtualId) { } TEST(DisplayIdTest, createHalVirtualId) { - HalVirtualDisplayId id(42); + const HalVirtualDisplayId id(42); EXPECT_TRUE(VirtualDisplayId::tryCast(id)); EXPECT_TRUE(HalVirtualDisplayId::tryCast(id)); EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id)); diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index c18be7a8e8..83b1b68a17 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -79,7 +79,7 @@ void Display::setSecure(bool secure) { } bool Display::isVirtual() const { - return VirtualDisplayId::tryCast(mId).has_value(); + return mId.isVirtual(); } std::optional Display::getDisplayId() const { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index c2d09c910d..ef3e77d4da 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -89,7 +89,7 @@ public: return mCompositionDisplay; } - bool isVirtual() const { return VirtualDisplayId::tryCast(getId()).has_value(); } + bool isVirtual() const { return getId().isVirtual(); } bool isPrimary() const { return mIsPrimary; } // isSecure indicates whether this display can be trusted to display diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp index 4b5a68cefa..d69bfafd2a 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp @@ -316,7 +316,7 @@ status_t VirtualDisplaySurface::setAsyncMode(bool async) { status_t VirtualDisplaySurface::dequeueBuffer(Source source, PixelFormat format, uint64_t usage, int* sslot, sp* fence) { - LOG_ALWAYS_FATAL_IF(GpuVirtualDisplayId::tryCast(mDisplayId).has_value()); + LOG_ALWAYS_FATAL_IF(mDisplayId.isVirtual()); status_t result = mSource[source]->dequeueBuffer(sslot, fence, mSinkBufferWidth, mSinkBufferHeight, @@ -616,7 +616,7 @@ void VirtualDisplaySurface::resetPerFrameState() { } status_t VirtualDisplaySurface::refreshOutputBuffer() { - LOG_ALWAYS_FATAL_IF(GpuVirtualDisplayId::tryCast(mDisplayId).has_value()); + LOG_ALWAYS_FATAL_IF(mDisplayId.isVirtual()); if (mOutputProducerSlot >= 0) { mSource[SOURCE_SINK]->cancelBuffer( -- GitLab From 0b10360a95147b38090bb3ecf8a4ef44cbb2f3f0 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 21 May 2024 20:37:49 +0000 Subject: [PATCH 371/465] SF: initiateDisplayModeChanges requires another commit When initiateDisplayModeChanges decides to change the mode, another commit is required as the call to finalizeDisplayModeChange resides within commit. Bug: 341152836 Change-Id: Ic60e144e113521b9143fa60e5fed46aa562ddfe4 Test: android.view.surfacecontrol.cts.ChoreographerNativeTest#testRefreshRateCallbacksIsSyncedWithDisplayManager --- services/surfaceflinger/SurfaceFlinger.cpp | 13 +++---------- services/surfaceflinger/SurfaceFlinger.h | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b2ca572a49..90484b196f 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1414,12 +1414,11 @@ void SurfaceFlinger::applyActiveMode(const sp& display) { } } -bool SurfaceFlinger::initiateDisplayModeChanges() { +void SurfaceFlinger::initiateDisplayModeChanges() { ATRACE_CALL(); std::optional displayToUpdateImmediately; - bool mustComposite = false; for (const auto& [id, physical] : mPhysicalDisplays) { const auto display = getDisplayDeviceLocked(id); if (!display) continue; @@ -1471,11 +1470,7 @@ bool SurfaceFlinger::initiateDisplayModeChanges() { mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline); if (outTimeline.refreshRequired) { - if (FlagManager::getInstance().vrr_bugfix_24q4()) { - mustComposite = true; - } else { - scheduleComposite(FrameHint::kNone); - } + scheduleComposite(FrameHint::kNone); } else { // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange` // for all displays. This was only needed when the loop iterated over `mDisplays` rather @@ -1493,8 +1488,6 @@ bool SurfaceFlinger::initiateDisplayModeChanges() { applyActiveMode(display); } } - - return mustComposite; } void SurfaceFlinger::disableExpensiveRendering() { @@ -2674,7 +2667,7 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, ? &mLayerHierarchyBuilder.getHierarchy() : nullptr, updateAttachedChoreographer); - mustComposite |= initiateDisplayModeChanges(); + initiateDisplayModeChanges(); } updateCursorAsync(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index d56072a46c..0682e95dbb 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -737,7 +737,7 @@ private: status_t setActiveModeFromBackdoor(const sp&, DisplayModeId, Fps minFps, Fps maxFps); - bool initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext); + void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext); void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext); // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler. -- GitLab From eafc18ad085dcd727277729aee2f74edc4df589b Mon Sep 17 00:00:00 2001 From: Wenhui Yang Date: Tue, 14 May 2024 17:15:52 +0000 Subject: [PATCH 372/465] Capture transaction traces before system reboot Bug: 299937754 Test: bugreport - data/misc/wmtrace/systemRestart_transactions.winscope Test: go/winscope Change-Id: Icfb10a9e69fd2ca7f4657564b109f8da126f7dc7 --- libs/gui/SurfaceComposerClient.cpp | 4 ++++ libs/gui/aidl/android/gui/ISurfaceComposer.aidl | 7 +++++++ libs/gui/include/gui/SurfaceComposerClient.h | 2 ++ libs/gui/tests/Surface_test.cpp | 2 ++ services/surfaceflinger/SurfaceFlinger.cpp | 8 +++++++- services/surfaceflinger/SurfaceFlinger.h | 1 + 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 3743025b7b..1977ce614c 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -3113,6 +3113,10 @@ status_t SurfaceComposerClient::removeWindowInfosListener( ->removeWindowInfosListener(windowInfosListener, ComposerServiceAIDL::getComposerService()); } + +void SurfaceComposerClient::notifyShutdown() { + ComposerServiceAIDL::getComposerService()->notifyShutdown(); +} // ---------------------------------------------------------------------------- status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs, diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl index a2549e7e3f..854045a17f 100644 --- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl +++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl @@ -571,4 +571,11 @@ interface ISurfaceComposer { @nullable StalledTransactionInfo getStalledTransactionInfo(int pid); SchedulingPolicy getSchedulingPolicy(); + + /** + * Notifies the SurfaceFlinger that the ShutdownThread is running. When it is called, + * transaction traces will be captured and writted into a file. + * This method should not block the ShutdownThread therefore it's handled asynchronously. + */ + oneway void notifyShutdown(); } diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 49b0a7d0c9..64fbb4342b 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -825,6 +825,8 @@ public: nullptr); status_t removeWindowInfosListener(const sp& windowInfosListener); + static void notifyShutdown(); + protected: ReleaseCallbackThread mReleaseCallbackThread; diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp index f4b059c39b..8aec030ea3 100644 --- a/libs/gui/tests/Surface_test.cpp +++ b/libs/gui/tests/Surface_test.cpp @@ -984,6 +984,8 @@ public: return binder::Status::ok(); } + binder::Status notifyShutdown() override { return binder::Status::ok(); } + protected: IBinder* onAsBinder() override { return nullptr; } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 496185e49b..68bd6a2727 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -984,8 +984,9 @@ void SurfaceFlinger::initTransactionTraceWriter() { ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str()); return; } - mTransactionTracing->flush(); + ALOGD("TransactionTraceWriter: writing file=%s", filename.c_str()); mTransactionTracing->writeToFile(filename); + mTransactionTracing->flush(); }; if (std::this_thread::get_id() == mMainThreadId) { writeFn(); @@ -10343,6 +10344,11 @@ binder::Status SurfaceComposerAIDL::getSchedulingPolicy(gui::SchedulingPolicy* o return gui::getSchedulingPolicy(outPolicy); } +binder::Status SurfaceComposerAIDL::notifyShutdown() { + TransactionTraceWriter::getInstance().invoke("systemShutdown_", /* overwrite= */ false); + return ::android::binder::Status::ok(); +} + status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) { if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) { IPCThreadState* ipc = IPCThreadState::self(); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 24444241c9..9a2f018b8c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -1677,6 +1677,7 @@ public: binder::Status getStalledTransactionInfo( int pid, std::optional* outInfo) override; binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override; + binder::Status notifyShutdown() override; private: static const constexpr bool kUsePermissionCache = true; -- GitLab From 3348c74c8e5cf51ea48a6f86e891c003cc2e3d5a Mon Sep 17 00:00:00 2001 From: Russell Myers Date: Mon, 29 Apr 2024 20:22:42 +0000 Subject: [PATCH 373/465] Create more expressive shader cache config. Devices such as Wear and others may not need one of the many portions of the cache that are available by default. Rather than simply configuring just HDR and / or the entire cache, we offer a more nuanced configuration that can be turned off via system properties. Bug: 325045840 Test: Local wear builds with different configuration settings. Change-Id: I0aded11eeab45d3684979f24c12f3676deb6d27b --- .../include/renderengine/RenderEngine.h | 17 ++++- .../include/renderengine/mock/RenderEngine.h | 2 +- libs/renderengine/skia/Cache.cpp | 66 +++++++++++++------ libs/renderengine/skia/Cache.h | 11 +++- libs/renderengine/skia/SkiaRenderEngine.cpp | 4 +- libs/renderengine/skia/SkiaRenderEngine.h | 2 +- libs/renderengine/tests/RenderEngineTest.cpp | 4 +- .../tests/RenderEngineThreadedTest.cpp | 21 +++++- .../threaded/RenderEngineThreaded.cpp | 29 ++++---- .../threaded/RenderEngineThreaded.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 29 +++++++- 11 files changed, 137 insertions(+), 50 deletions(-) diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h index 980d913d40..7207394356 100644 --- a/libs/renderengine/include/renderengine/RenderEngine.h +++ b/libs/renderengine/include/renderengine/RenderEngine.h @@ -88,6 +88,21 @@ enum class Protection { PROTECTED = 2, }; +// Toggles for skipping or enabling priming of particular shaders. +struct PrimeCacheConfig { + bool cacheHolePunchLayer = true; + bool cacheSolidLayers = true; + bool cacheSolidDimmedLayers = true; + bool cacheImageLayers = true; + bool cacheImageDimmedLayers = true; + bool cacheClippedLayers = true; + bool cacheShadowLayers = true; + bool cachePIPImageLayers = true; + bool cacheTransparentImageDimmedLayers = true; + bool cacheClippedDimmedImageLayers = true; + bool cacheUltraHDR = true; +}; + class RenderEngine { public: enum class ContextPriority { @@ -145,7 +160,7 @@ public: // This interface, while still in use until a suitable replacement is built, // should be considered deprecated, minus some methods which still may be // used to support legacy behavior. - virtual std::future primeCache(bool shouldPrimeUltraHDR) = 0; + virtual std::future primeCache(PrimeCacheConfig config) = 0; // dump the extension strings. always call the base class. virtual void dump(std::string& result) = 0; diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h index a58a65ca9f..a8c242a86f 100644 --- a/libs/renderengine/include/renderengine/mock/RenderEngine.h +++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h @@ -33,7 +33,7 @@ public: RenderEngine(); ~RenderEngine() override; - MOCK_METHOD1(primeCache, std::future(bool)); + MOCK_METHOD1(primeCache, std::future(PrimeCacheConfig)); MOCK_METHOD1(dump, void(std::string&)); MOCK_CONST_METHOD0(getMaxTextureSize, size_t()); MOCK_CONST_METHOD0(getMaxViewportDims, size_t()); diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp index abe0d9b0a6..d2468700e4 100644 --- a/libs/renderengine/skia/Cache.cpp +++ b/libs/renderengine/skia/Cache.cpp @@ -630,7 +630,7 @@ static void drawP3ImageLayers(SkiaRenderEngine* renderengine, const DisplaySetti // kFlushAfterEveryLayer = true // in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp // gPrintSKSL = true -void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) { +void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig config) { const int previousCount = renderengine->reportShadersCompiled(); if (previousCount) { ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount); @@ -694,13 +694,24 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUlt impl::ExternalTexture>(srcBuffer, *renderengine, impl::ExternalTexture::Usage::READABLE | impl::ExternalTexture::Usage::WRITEABLE); - drawHolePunchLayer(renderengine, display, dstTexture); - drawSolidLayers(renderengine, display, dstTexture); - drawSolidLayers(renderengine, p3Display, dstTexture); - drawSolidDimmedLayers(renderengine, display, dstTexture); - drawShadowLayers(renderengine, display, srcTexture); - drawShadowLayers(renderengine, p3Display, srcTexture); + if (config.cacheHolePunchLayer) { + drawHolePunchLayer(renderengine, display, dstTexture); + } + + if (config.cacheSolidLayers) { + drawSolidLayers(renderengine, display, dstTexture); + drawSolidLayers(renderengine, p3Display, dstTexture); + } + + if (config.cacheSolidDimmedLayers) { + drawSolidDimmedLayers(renderengine, display, dstTexture); + } + + if (config.cacheShadowLayers) { + drawShadowLayers(renderengine, display, srcTexture); + drawShadowLayers(renderengine, p3Display, srcTexture); + } if (renderengine->supportsBackgroundBlur()) { drawBlurLayers(renderengine, display, dstTexture); @@ -737,27 +748,40 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUlt } for (auto texture : textures) { - drawImageLayers(renderengine, display, dstTexture, texture); + if (config.cacheImageLayers) { + drawImageLayers(renderengine, display, dstTexture, texture); + } - drawImageDimmedLayers(renderengine, display, dstTexture, texture); - drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture); - drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture); + if (config.cacheImageDimmedLayers) { + drawImageDimmedLayers(renderengine, display, dstTexture, texture); + drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture); + drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture); + } - // Draw layers for b/185569240. - drawClippedLayers(renderengine, display, dstTexture, texture); + if (config.cacheClippedLayers) { + // Draw layers for b/185569240. + drawClippedLayers(renderengine, display, dstTexture, texture); + } } - drawPIPImageLayer(renderengine, display, dstTexture, externalTexture); + if (config.cachePIPImageLayers) { + drawPIPImageLayer(renderengine, display, dstTexture, externalTexture); + } - drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture); - drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture); - drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture); - drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture, - externalTexture); + if (config.cacheTransparentImageDimmedLayers) { + drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, + externalTexture); + drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture); + drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture); + drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture, + externalTexture); + } - drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture); + if (config.cacheClippedDimmedImageLayers) { + drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture); + } - if (shouldPrimeUltraHDR) { + if (config.cacheUltraHDR) { drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture); drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture); diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h index 62f6705c89..259432f91c 100644 --- a/libs/renderengine/skia/Cache.h +++ b/libs/renderengine/skia/Cache.h @@ -16,16 +16,21 @@ #pragma once -namespace android::renderengine::skia { +namespace android::renderengine { + +struct PrimeCacheConfig; + +namespace skia { class SkiaRenderEngine; class Cache { public: - static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR); + static void primeShaderCache(SkiaRenderEngine*, PrimeCacheConfig config); private: Cache() = default; }; -} // namespace android::renderengine::skia +} // namespace skia +} // namespace android::renderengine diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index 325a91178a..befca35483 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -246,8 +246,8 @@ namespace skia { using base::StringAppendF; -std::future SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) { - Cache::primeShaderCache(this, shouldPrimeUltraHDR); +std::future SkiaRenderEngine::primeCache(PrimeCacheConfig config) { + Cache::primeShaderCache(this, config); return {}; } diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h index 38db8100dc..c8f9241257 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.h +++ b/libs/renderengine/skia/SkiaRenderEngine.h @@ -62,7 +62,7 @@ public: SkiaRenderEngine(Threaded, PixelFormat pixelFormat, BlurAlgorithm); ~SkiaRenderEngine() override; - std::future primeCache(bool shouldPrimeUltraHDR) override final; + std::future primeCache(PrimeCacheConfig config) override final; void cleanupPostRender() override final; bool supportsBackgroundBlur() override final { return mBlurFilter != nullptr; diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index cb8b016e12..4dcaff9ec8 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -3141,7 +3141,9 @@ TEST_P(RenderEngineTest, primeShaderCache) { } initializeRenderEngine(); - auto fut = mRE->primeCache(false); + PrimeCacheConfig config; + config.cacheUltraHDR = false; + auto fut = mRE->primeCache(config); if (fut.valid()) { fut.wait(); } diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp index d56dbb2a7b..bdd94023cc 100644 --- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp +++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp @@ -25,6 +25,7 @@ namespace android { +using renderengine::PrimeCacheConfig; using testing::_; using testing::Eq; using testing::Mock; @@ -48,9 +49,25 @@ TEST_F(RenderEngineThreadedTest, dump) { mThreadedRE->dump(testString); } +MATCHER_P(EqConfig, other, "Equality for prime cache config") { + return arg.cacheHolePunchLayer == other.cacheHolePunchLayer && + arg.cacheSolidLayers == other.cacheSolidLayers && + arg.cacheSolidDimmedLayers == other.cacheSolidDimmedLayers && + arg.cacheImageLayers == other.cacheImageLayers && + arg.cacheImageDimmedLayers == other.cacheImageDimmedLayers && + arg.cacheClippedLayers == other.cacheClippedLayers && + arg.cacheShadowLayers == other.cacheShadowLayers && + arg.cachePIPImageLayers == other.cachePIPImageLayers && + arg.cacheTransparentImageDimmedLayers == other.cacheTransparentImageDimmedLayers && + arg.cacheClippedDimmedImageLayers == other.cacheClippedDimmedImageLayers && + arg.cacheUltraHDR == other.cacheUltraHDR; +} + TEST_F(RenderEngineThreadedTest, primeCache) { - EXPECT_CALL(*mRenderEngine, primeCache(false)); - mThreadedRE->primeCache(false); + PrimeCacheConfig config; + config.cacheUltraHDR = false; + EXPECT_CALL(*mRenderEngine, primeCache(EqConfig(config))); + mThreadedRE->primeCache(config); // need to call ANY synchronous function after primeCache to ensure that primeCache has // completed asynchronously before the test completes execution. mThreadedRE->getContextPriority(); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp index f4cebc05ec..d27c151e72 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.cpp +++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp @@ -130,7 +130,7 @@ void RenderEngineThreaded::waitUntilInitialized() const { } } -std::future RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) { +std::future RenderEngineThreaded::primeCache(PrimeCacheConfig config) { const auto resultPromise = std::make_shared>(); std::future resultFuture = resultPromise->get_future(); ATRACE_CALL(); @@ -138,20 +138,19 @@ std::future RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) { // for the futures. { std::lock_guard lock(mThreadMutex); - mFunctionCalls.push( - [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) { - ATRACE_NAME("REThreaded::primeCache"); - if (setSchedFifo(false) != NO_ERROR) { - ALOGW("Couldn't set SCHED_OTHER for primeCache"); - } - - instance.primeCache(shouldPrimeUltraHDR); - resultPromise->set_value(); - - if (setSchedFifo(true) != NO_ERROR) { - ALOGW("Couldn't set SCHED_FIFO for primeCache"); - } - }); + mFunctionCalls.push([resultPromise, config](renderengine::RenderEngine& instance) { + ATRACE_NAME("REThreaded::primeCache"); + if (setSchedFifo(false) != NO_ERROR) { + ALOGW("Couldn't set SCHED_OTHER for primeCache"); + } + + instance.primeCache(config); + resultPromise->set_value(); + + if (setSchedFifo(true) != NO_ERROR) { + ALOGW("Couldn't set SCHED_FIFO for primeCache"); + } + }); } mCondition.notify_one(); diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h index d440c961e7..d4997d6c93 100644 --- a/libs/renderengine/threaded/RenderEngineThreaded.h +++ b/libs/renderengine/threaded/RenderEngineThreaded.h @@ -41,7 +41,7 @@ public: RenderEngineThreaded(CreateInstanceFactory factory); ~RenderEngineThreaded() override; - std::future primeCache(bool shouldPrimeUltraHDR) override; + std::future primeCache(PrimeCacheConfig config) override; void dump(std::string& result) override; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 9d29c416cd..bd5accc0f3 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -938,9 +938,34 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { } mRenderEnginePrimeCacheFuture.callOnce([this] { - const bool shouldPrimeUltraHDR = + renderengine::PrimeCacheConfig config; + config.cacheHolePunchLayer = + base::GetBoolProperty("debug.sf.prime_shader_cache.hole_punch"s, true); + config.cacheSolidLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.solid_layers"s, true); + config.cacheSolidDimmedLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.solid_dimmed_layers"s, true); + config.cacheImageLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.image_layers"s, true); + config.cacheImageDimmedLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.image_dimmed_layers"s, true); + config.cacheClippedLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.clipped_layers"s, true); + config.cacheShadowLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.shadow_layers"s, true); + config.cachePIPImageLayers = + base::GetBoolProperty("debug.sf.prime_shader_cache.pip_image_layers"s, true); + config.cacheTransparentImageDimmedLayers = base:: + GetBoolProperty("debug.sf.prime_shader_cache.transparent_image_dimmed_layers"s, + true); + config.cacheClippedDimmedImageLayers = base:: + GetBoolProperty("debug.sf.prime_shader_cache.clipped_dimmed_image_layers"s, + true); + // ro.surface_flinger.prime_chader_cache.ultrahdr exists as a previous ro property + // which we maintain for backwards compatibility. + config.cacheUltraHDR = base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false); - return getRenderEngine().primeCache(shouldPrimeUltraHDR); + return getRenderEngine().primeCache(config); }); if (setSchedFifo(true) != NO_ERROR) { -- GitLab From 0963ded01e09a047c05e2093470e0e4dead3e9cf Mon Sep 17 00:00:00 2001 From: Rocky Fang Date: Wed, 22 May 2024 00:04:43 +0000 Subject: [PATCH 374/465] Add clean up code into a flag Bug: 329020894 Test: injecting log, seeing expected result Change-Id: I49fac4fd6ecd7e489a49fcacb21237439ce262d9 --- services/sensorservice/SensorService.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 70ca7025d4..31b7f8886c 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -1273,7 +1273,10 @@ bool SensorService::threadLoop() { } else { int handle = mSensorEventBuffer[i].dynamic_sensor_meta.handle; disconnectDynamicSensor(handle, activeConnections); - device.cleanupDisconnectedDynamicSensor(handle); + if (sensorservice_flags:: + sensor_service_clear_dynamic_sensor_data_at_the_end()) { + device.cleanupDisconnectedDynamicSensor(handle); + } } } } -- GitLab From 9e0017e9748e6cd1ed681f4d3aa9b888a02bfa3c Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 22 May 2024 19:02:44 +0000 Subject: [PATCH 375/465] Allow child layers to unset trusted overlay state 2/2 Test: presubmit Bug: 339701674 Change-Id: I1a94b5e5dc1fa64a9e1eb3330b5c5b03af8d2b16 --- libs/gui/Android.bp | 1 + libs/gui/LayerState.cpp | 12 +-- libs/gui/SurfaceComposerClient.cpp | 9 +- libs/gui/android/gui/TrustedOverlay.aidl | 45 ++++++++++ libs/gui/include/gui/LayerState.h | 3 +- libs/gui/include/gui/SurfaceComposerClient.h | 2 + .../surfaceflinger/FrontEnd/LayerSnapshot.h | 2 +- .../FrontEnd/LayerSnapshotBuilder.cpp | 19 ++++- .../FrontEnd/RequestedLayerState.cpp | 2 +- services/surfaceflinger/Layer.cpp | 2 +- services/surfaceflinger/LayerProtoHelper.cpp | 3 +- services/surfaceflinger/SurfaceFlinger.cpp | 2 +- .../Tracing/TransactionProtoParser.cpp | 6 +- .../surfaceflinger/common/FlagManager.cpp | 2 + .../common/include/common/FlagManager.h | 1 + .../surfaceflinger_flags_new.aconfig | 13 ++- .../tests/unittests/LayerHierarchyTest.h | 4 +- .../tests/unittests/LayerSnapshotTest.cpp | 83 ++++++++++++++++++- 18 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 libs/gui/android/gui/TrustedOverlay.aidl diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 6c45746335..2547297ff8 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -87,6 +87,7 @@ filegroup { "android/gui/DropInputMode.aidl", "android/gui/StalledTransactionInfo.aidl", "android/**/TouchOcclusionMode.aidl", + "android/gui/TrustedOverlay.aidl", ], } diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index 0a2879975b..c82bde9a83 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -89,7 +89,7 @@ layer_state_t::layer_state_t() frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE), fixedTransformHint(ui::Transform::ROT_INVALID), autoRefresh(false), - isTrustedOverlay(false), + trustedOverlay(gui::TrustedOverlay::UNSET), bufferCrop(Rect::INVALID_RECT), destinationFrame(Rect::INVALID_RECT), dropInputMode(gui::DropInputMode::NONE) { @@ -179,7 +179,7 @@ status_t layer_state_t::write(Parcel& output) const SAFE_PARCEL(output.write, stretchEffect); SAFE_PARCEL(output.write, bufferCrop); SAFE_PARCEL(output.write, destinationFrame); - SAFE_PARCEL(output.writeBool, isTrustedOverlay); + SAFE_PARCEL(output.writeInt32, static_cast(trustedOverlay)); SAFE_PARCEL(output.writeUint32, static_cast(dropInputMode)); @@ -308,7 +308,9 @@ status_t layer_state_t::read(const Parcel& input) SAFE_PARCEL(input.read, stretchEffect); SAFE_PARCEL(input.read, bufferCrop); SAFE_PARCEL(input.read, destinationFrame); - SAFE_PARCEL(input.readBool, &isTrustedOverlay); + uint32_t trustedOverlayInt; + SAFE_PARCEL(input.readUint32, &trustedOverlayInt); + trustedOverlay = static_cast(trustedOverlayInt); uint32_t mode; SAFE_PARCEL(input.readUint32, &mode); @@ -674,7 +676,7 @@ void layer_state_t::merge(const layer_state_t& other) { } if (other.what & eTrustedOverlayChanged) { what |= eTrustedOverlayChanged; - isTrustedOverlay = other.isTrustedOverlay; + trustedOverlay = other.trustedOverlay; } if (other.what & eStretchChanged) { what |= eStretchChanged; @@ -779,7 +781,7 @@ uint64_t layer_state_t::diff(const layer_state_t& other) const { CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy); CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint); CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh); - CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay); + CHECK_DIFF(diff, eTrustedOverlayChanged, other, trustedOverlay); CHECK_DIFF(diff, eStretchChanged, other, stretchEffect); CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop); CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame); diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index 7aaaebbc8e..e0f1b03e26 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -2175,6 +2175,13 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAutoR SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrustedOverlay( const sp& sc, bool isTrustedOverlay) { + return setTrustedOverlay(sc, + isTrustedOverlay ? gui::TrustedOverlay::ENABLED + : gui::TrustedOverlay::UNSET); +} + +SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrustedOverlay( + const sp& sc, gui::TrustedOverlay trustedOverlay) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; @@ -2182,7 +2189,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrust } s->what |= layer_state_t::eTrustedOverlayChanged; - s->isTrustedOverlay = isTrustedOverlay; + s->trustedOverlay = trustedOverlay; return *this; } diff --git a/libs/gui/android/gui/TrustedOverlay.aidl b/libs/gui/android/gui/TrustedOverlay.aidl new file mode 100644 index 0000000000..06fb5f0bd5 --- /dev/null +++ b/libs/gui/android/gui/TrustedOverlay.aidl @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.gui; + + +/** + * Trusted overlay state prevents layers from being considered as obscuring for + * input occlusion detection purposes. + * + * @hide + */ +@Backing(type="int") +enum TrustedOverlay { + /** + * The default, layer will inherit the state from its parents. If the parent state is also + * unset, the layer will be considered as untrusted. + */ + UNSET, + + /** + * Treats this layer and all its children as an untrusted overlay. This will override any + * state set by its parent layers. + */ + DISABLED, + + /** + * Treats this layer and all its children as a trusted overlay unless the child layer + * explicitly disables its trusted state. + */ + ENABLED +} diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index ebdf2326d6..849f66718a 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -388,7 +389,7 @@ struct layer_state_t { // An inherited state that indicates that this surface control and its children // should be trusted for input occlusion detection purposes - bool isTrustedOverlay; + gui::TrustedOverlay trustedOverlay; // Stretch effect to be applied to this layer StretchEffect stretchEffect; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 987efe01ba..50f2d17f51 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -719,6 +719,8 @@ public: // Sets that this surface control and its children are trusted overlays for input Transaction& setTrustedOverlay(const sp& sc, bool isTrustedOverlay); + Transaction& setTrustedOverlay(const sp& sc, + gui::TrustedOverlay trustedOverlay); // Queues up transactions using this token in SurfaceFlinger. By default, all transactions // from a client are placed on the same queue. This can be used to prevent multiple diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h index eef8dff94c..398e64a4f1 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h +++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h @@ -84,7 +84,7 @@ struct LayerSnapshot : public compositionengine::LayerFECompositionState { // is a mirror root bool ignoreLocalTransform; gui::DropInputMode dropInputMode; - bool isTrustedOverlay; + gui::TrustedOverlay trustedOverlay; gui::GameMode gameMode; scheduler::LayerInfo::FrameRate frameRate; scheduler::LayerInfo::FrameRate inheritedFrameRate; diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index f10bb33a2f..87706d8491 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -373,7 +374,7 @@ LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() { snapshot.relativeLayerMetadata.mMap.clear(); snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED; snapshot.dropInputMode = gui::DropInputMode::NONE; - snapshot.isTrustedOverlay = false; + snapshot.trustedOverlay = gui::TrustedOverlay::UNSET; snapshot.gameMode = gui::GameMode::Unsupported; snapshot.frameRate = {}; snapshot.fixedTransformHint = ui::Transform::ROT_INVALID; @@ -750,7 +751,19 @@ void LayerSnapshotBuilder::updateSnapshot(LayerSnapshot& snapshot, const Args& a } if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) { - snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay; + switch (requested.trustedOverlay) { + case gui::TrustedOverlay::UNSET: + snapshot.trustedOverlay = parentSnapshot.trustedOverlay; + break; + case gui::TrustedOverlay::DISABLED: + snapshot.trustedOverlay = FlagManager::getInstance().override_trusted_overlay() + ? requested.trustedOverlay + : parentSnapshot.trustedOverlay; + break; + case gui::TrustedOverlay::ENABLED: + snapshot.trustedOverlay = requested.trustedOverlay; + break; + } } if (snapshot.isHiddenByPolicyFromParent && @@ -1125,7 +1138,7 @@ void LayerSnapshotBuilder::updateInput(LayerSnapshot& snapshot, // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state // if it was set by WM for a known system overlay - if (snapshot.isTrustedOverlay) { + if (snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED) { snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY; } diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index f5e5b0233e..3cf45a7fbb 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -119,7 +119,7 @@ RequestedLayerState::RequestedLayerState(const LayerCreationArgs& args) shadowRadius = 0.f; fixedTransformHint = ui::Transform::ROT_INVALID; destinationFrame.makeInvalid(); - isTrustedOverlay = false; + trustedOverlay = gui::TrustedOverlay::UNSET; dropInputMode = gui::DropInputMode::NONE; dimmingEnabled = true; defaultFrameRateCompatibility = static_cast(scheduler::FrameRateCompatibility::Default); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 363b35c4f3..6b97e2f799 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3902,7 +3902,7 @@ bool Layer::isSimpleBufferUpdate(const layer_state_t& s) const { } if (s.what & layer_state_t::eTrustedOverlayChanged) { - if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) { + if (mDrawingState.isTrustedOverlay != (s.trustedOverlay == gui::TrustedOverlay::ENABLED)) { ATRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__); return false; } diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp index 753886ad24..496033b749 100644 --- a/services/surfaceflinger/LayerProtoHelper.cpp +++ b/services/surfaceflinger/LayerProtoHelper.cpp @@ -382,7 +382,8 @@ void LayerProtoHelper::writeSnapshotToProto(perfetto::protos::LayerProto* layerI layerInfo->set_corner_radius( (snapshot.roundedCorner.radius.x + snapshot.roundedCorner.radius.y) / 2.0); layerInfo->set_background_blur_radius(snapshot.backgroundBlurRadius); - layerInfo->set_is_trusted_overlay(snapshot.isTrustedOverlay); + layerInfo->set_is_trusted_overlay(snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED); + // TODO(b/339701674) update protos LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform()); LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(), [&]() { return layerInfo->mutable_position(); }); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 28d80188ff..059edad467 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5700,7 +5700,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded; } if (what & layer_state_t::eTrustedOverlayChanged) { - if (layer->setTrustedOverlay(s.isTrustedOverlay)) { + if (layer->setTrustedOverlay(s.trustedOverlay == gui::TrustedOverlay::ENABLED)) { flags |= eTraversalNeeded; } } diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index b3e9fab261..fc496b2982 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -247,7 +247,8 @@ perfetto::protos::LayerState TransactionProtoParser::toProto( proto.set_auto_refresh(layer.autoRefresh); } if (layer.what & layer_state_t::eTrustedOverlayChanged) { - proto.set_is_trusted_overlay(layer.isTrustedOverlay); + proto.set_is_trusted_overlay(layer.trustedOverlay == gui::TrustedOverlay::ENABLED); + // TODO(b/339701674) update protos } if (layer.what & layer_state_t::eBufferCropChanged) { LayerProtoHelper::writeToProto(layer.bufferCrop, proto.mutable_buffer_crop()); @@ -515,7 +516,8 @@ void TransactionProtoParser::fromProto(const perfetto::protos::LayerState& proto layer.autoRefresh = proto.auto_refresh(); } if (proto.what() & layer_state_t::eTrustedOverlayChanged) { - layer.isTrustedOverlay = proto.is_trusted_overlay(); + layer.trustedOverlay = proto.is_trusted_overlay() ? gui::TrustedOverlay::ENABLED + : gui::TrustedOverlay::UNSET; } if (proto.what() & layer_state_t::eBufferCropChanged) { LayerProtoHelper::readFromProto(proto.buffer_crop(), layer.bufferCrop); diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 121629fd10..57b170fba3 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -147,6 +147,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(detached_mirror); DUMP_READ_ONLY_FLAG(commit_not_composited); DUMP_READ_ONLY_FLAG(local_tonemap_screenshots); + DUMP_READ_ONLY_FLAG(override_trusted_overlay); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -244,6 +245,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, ""); FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, ""); FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, ""); FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots"); +FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 4cf4453980..9517ff7efb 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -86,6 +86,7 @@ public: bool detached_mirror() const; bool commit_not_composited() const; bool local_tonemap_screenshots() const; + bool override_trusted_overlay() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 5b94f07dff..02d8819785 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -52,7 +52,7 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -} # deprecate_vsync_sf +} # detached_mirror flag { name: "frame_rate_category_mrr" @@ -84,6 +84,17 @@ flag { is_fixed_read_only: true } # local_tonemap_screenshots + flag { + name: "override_trusted_overlay" + namespace: "window_surfaces" + description: "Allow child to disable trusted overlay set by a parent layer" + bug: "339701674" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # override_trusted_overlay + flag { name: "vrr_bugfix_24q4" namespace: "core_graphics" diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h index fd15eef54b..8b3303c809 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h @@ -494,14 +494,14 @@ protected: mLifecycleManager.applyTransactions(transactions); } - void setTrustedOverlay(uint32_t id, bool isTrustedOverlay) { + void setTrustedOverlay(uint32_t id, gui::TrustedOverlay trustedOverlay) { std::vector transactions; transactions.emplace_back(); transactions.back().states.push_back({}); transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged; transactions.back().states.front().layerId = id; - transactions.back().states.front().state.isTrustedOverlay = isTrustedOverlay; + transactions.back().states.front().state.trustedOverlay = trustedOverlay; mLifecycleManager.applyTransactions(transactions); } diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index ce4d798dd8..8acabe3b54 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -1247,7 +1247,7 @@ TEST_F(LayerSnapshotTest, setShadowRadius) { TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) { hideLayer(1); - setTrustedOverlay(1, true); + setTrustedOverlay(1, gui::TrustedOverlay::ENABLED); Region touch{Rect{0, 0, 1000, 1000}}; setTouchableRegion(1, touch); @@ -1439,4 +1439,85 @@ TEST_F(LayerSnapshotTest, mirroredHierarchyIgnoresLocalTransform) { EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.ty(), 220); } +TEST_F(LayerSnapshotTest, overrideParentTrustedOverlayState) { + SET_FLAG_FOR_TEST(flags::override_trusted_overlay, true); + hideLayer(1); + setTrustedOverlay(1, gui::TrustedOverlay::ENABLED); + + Region touch{Rect{0, 0, 1000, 1000}}; + setTouchableRegion(1, touch); + setTouchableRegion(11, touch); + setTouchableRegion(111, touch); + + UPDATE_AND_VERIFY(mSnapshotBuilder, {2}); + EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + + // disable trusted overlay and override parent state + setTrustedOverlay(11, gui::TrustedOverlay::DISABLED); + UPDATE_AND_VERIFY(mSnapshotBuilder, {2}); + EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_FALSE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_FALSE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + + // unset state and go back to default behavior of inheriting + // state + setTrustedOverlay(11, gui::TrustedOverlay::UNSET); + UPDATE_AND_VERIFY(mSnapshotBuilder, {2}); + EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); +} + +TEST_F(LayerSnapshotTest, doNotOverrideParentTrustedOverlayState) { + SET_FLAG_FOR_TEST(flags::override_trusted_overlay, false); + hideLayer(1); + setTrustedOverlay(1, gui::TrustedOverlay::ENABLED); + + Region touch{Rect{0, 0, 1000, 1000}}; + setTouchableRegion(1, touch); + setTouchableRegion(11, touch); + setTouchableRegion(111, touch); + + UPDATE_AND_VERIFY(mSnapshotBuilder, {2}); + EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + + // disable trusted overlay but flag is disabled so this behaves + // as UNSET + setTrustedOverlay(11, gui::TrustedOverlay::DISABLED); + UPDATE_AND_VERIFY(mSnapshotBuilder, {2}); + EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + + // unset state and go back to default behavior of inheriting + // state + setTrustedOverlay(11, gui::TrustedOverlay::UNSET); + UPDATE_AND_VERIFY(mSnapshotBuilder, {2}); + EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); + EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test( + gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); +} + } // namespace android::surfaceflinger::frontend -- GitLab From 5be0865b7bf1b21b3888c77339060f7dc7dd8018 Mon Sep 17 00:00:00 2001 From: Alan Ding Date: Fri, 24 May 2024 01:48:07 +0000 Subject: [PATCH 376/465] Revert "ui: Refactor stable ID generation for GPU virtual displays" This reverts commit 78c90af2224b43467d854e356492527db0f8eb70. Reason for revert: Causes assert for certain GPU virtual display Ids in VirtualDisplaySurface.cpp (https://paste.googleplex.com/6474098414977024) Change-Id: I743312bbab4fd9903e069c3ed4710f9d7901be8d --- libs/ui/include/ui/DisplayId.h | 42 +++++++------------ libs/ui/tests/DisplayId_test.cpp | 23 ++-------- .../CompositionEngine/src/Display.cpp | 2 +- services/surfaceflinger/DisplayDevice.h | 2 +- .../DisplayHardware/VirtualDisplaySurface.cpp | 4 +- 5 files changed, 23 insertions(+), 50 deletions(-) diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h index 8a14db8ffa..3a31fa0848 100644 --- a/libs/ui/include/ui/DisplayId.h +++ b/libs/ui/include/ui/DisplayId.h @@ -20,7 +20,6 @@ #include #include -#include #include namespace android { @@ -39,9 +38,6 @@ struct DisplayId { constexpr DisplayId(const DisplayId&) = default; DisplayId& operator=(const DisplayId&) = default; - constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; } - constexpr bool isStable() const { return value & FLAG_STABLE; } - uint64_t value; // For deserialization. @@ -80,10 +76,10 @@ inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) { // DisplayId of a physical display, such as the internal display or externally connected display. struct PhysicalDisplayId : DisplayId { static constexpr ftl::Optional tryCast(DisplayId id) { - if (id.isVirtual()) { + if (id.value & FLAG_VIRTUAL) { return std::nullopt; } - return PhysicalDisplayId(id); + return {PhysicalDisplayId(id)}; } // Returns a stable ID based on EDID information. @@ -121,23 +117,25 @@ struct VirtualDisplayId : DisplayId { static constexpr uint64_t FLAG_GPU = 1ULL << 61; static constexpr std::optional tryCast(DisplayId id) { - if (id.isVirtual()) { - return VirtualDisplayId(id); + if (id.value & FLAG_VIRTUAL) { + return {VirtualDisplayId(id)}; } return std::nullopt; } protected: - explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {} + constexpr VirtualDisplayId(uint64_t flags, BaseId baseId) + : DisplayId(DisplayId::FLAG_VIRTUAL | flags | baseId) {} + explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {} }; struct HalVirtualDisplayId : VirtualDisplayId { - explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(baseId) {} + explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(0, baseId) {} static constexpr std::optional tryCast(DisplayId id) { - if (id.isVirtual() && !(id.value & FLAG_GPU)) { - return HalVirtualDisplayId(id); + if ((id.value & FLAG_VIRTUAL) && !(id.value & VirtualDisplayId::FLAG_GPU)) { + return {HalVirtualDisplayId(id)}; } return std::nullopt; } @@ -147,27 +145,17 @@ private: }; struct GpuVirtualDisplayId : VirtualDisplayId { - explicit constexpr GpuVirtualDisplayId(BaseId baseId) : VirtualDisplayId(FLAG_GPU | baseId) {} - - static constexpr std::optional fromUniqueId(const std::string& uniqueId) { - if (const auto hashOpt = ftl::stable_hash(uniqueId)) { - return GpuVirtualDisplayId(HashTag{}, *hashOpt); - } - return {}; - } + explicit constexpr GpuVirtualDisplayId(BaseId baseId) + : VirtualDisplayId(VirtualDisplayId::FLAG_GPU, baseId) {} static constexpr std::optional tryCast(DisplayId id) { - if (id.isVirtual() && (id.value & FLAG_GPU)) { - return GpuVirtualDisplayId(id); + if ((id.value & FLAG_VIRTUAL) && (id.value & VirtualDisplayId::FLAG_GPU)) { + return {GpuVirtualDisplayId(id)}; } return std::nullopt; } private: - struct HashTag {}; // Disambiguate with BaseId constructor. - constexpr GpuVirtualDisplayId(HashTag, uint64_t hash) - : VirtualDisplayId(FLAG_STABLE | FLAG_GPU | hash) {} - explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {} }; @@ -181,7 +169,7 @@ struct HalDisplayId : DisplayId { if (GpuVirtualDisplayId::tryCast(id)) { return std::nullopt; } - return HalDisplayId(id); + return {HalDisplayId(id)}; } private: diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp index 1115804954..8ddee7e740 100644 --- a/libs/ui/tests/DisplayId_test.cpp +++ b/libs/ui/tests/DisplayId_test.cpp @@ -24,7 +24,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) { constexpr uint8_t port = 1; constexpr uint16_t manufacturerId = 13; constexpr uint32_t modelHash = 42; - const PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash); + PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash); EXPECT_EQ(port, id.getPort()); EXPECT_EQ(manufacturerId, id.getManufacturerId()); EXPECT_FALSE(VirtualDisplayId::tryCast(id)); @@ -39,7 +39,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) { TEST(DisplayIdTest, createPhysicalIdFromPort) { constexpr uint8_t port = 3; - const PhysicalDisplayId id = PhysicalDisplayId::fromPort(port); + PhysicalDisplayId id = PhysicalDisplayId::fromPort(port); EXPECT_EQ(port, id.getPort()); EXPECT_FALSE(VirtualDisplayId::tryCast(id)); EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); @@ -52,22 +52,7 @@ TEST(DisplayIdTest, createPhysicalIdFromPort) { } TEST(DisplayIdTest, createGpuVirtualId) { - const GpuVirtualDisplayId id(42); - EXPECT_TRUE(VirtualDisplayId::tryCast(id)); - EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); - EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); - EXPECT_FALSE(PhysicalDisplayId::tryCast(id)); - EXPECT_FALSE(HalDisplayId::tryCast(id)); - - EXPECT_EQ(id, DisplayId::fromValue(id.value)); - EXPECT_EQ(id, DisplayId::fromValue(id.value)); -} - -TEST(DisplayIdTest, createGpuVirtualIdFromUniqueId) { - static const std::string kUniqueId("virtual:ui:DisplayId_test"); - const auto idOpt = GpuVirtualDisplayId::fromUniqueId(kUniqueId); - ASSERT_TRUE(idOpt.has_value()); - const GpuVirtualDisplayId id = idOpt.value(); + GpuVirtualDisplayId id(42); EXPECT_TRUE(VirtualDisplayId::tryCast(id)); EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); @@ -79,7 +64,7 @@ TEST(DisplayIdTest, createGpuVirtualIdFromUniqueId) { } TEST(DisplayIdTest, createHalVirtualId) { - const HalVirtualDisplayId id(42); + HalVirtualDisplayId id(42); EXPECT_TRUE(VirtualDisplayId::tryCast(id)); EXPECT_TRUE(HalVirtualDisplayId::tryCast(id)); EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id)); diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index 83b1b68a17..c18be7a8e8 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -79,7 +79,7 @@ void Display::setSecure(bool secure) { } bool Display::isVirtual() const { - return mId.isVirtual(); + return VirtualDisplayId::tryCast(mId).has_value(); } std::optional Display::getDisplayId() const { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index ef3e77d4da..c2d09c910d 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -89,7 +89,7 @@ public: return mCompositionDisplay; } - bool isVirtual() const { return getId().isVirtual(); } + bool isVirtual() const { return VirtualDisplayId::tryCast(getId()).has_value(); } bool isPrimary() const { return mIsPrimary; } // isSecure indicates whether this display can be trusted to display diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp index d69bfafd2a..4b5a68cefa 100644 --- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp +++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp @@ -316,7 +316,7 @@ status_t VirtualDisplaySurface::setAsyncMode(bool async) { status_t VirtualDisplaySurface::dequeueBuffer(Source source, PixelFormat format, uint64_t usage, int* sslot, sp* fence) { - LOG_ALWAYS_FATAL_IF(mDisplayId.isVirtual()); + LOG_ALWAYS_FATAL_IF(GpuVirtualDisplayId::tryCast(mDisplayId).has_value()); status_t result = mSource[source]->dequeueBuffer(sslot, fence, mSinkBufferWidth, mSinkBufferHeight, @@ -616,7 +616,7 @@ void VirtualDisplaySurface::resetPerFrameState() { } status_t VirtualDisplaySurface::refreshOutputBuffer() { - LOG_ALWAYS_FATAL_IF(mDisplayId.isVirtual()); + LOG_ALWAYS_FATAL_IF(GpuVirtualDisplayId::tryCast(mDisplayId).has_value()); if (mOutputProducerSlot >= 0) { mSource[SOURCE_SINK]->cancelBuffer( -- GitLab From c6e522e2af6734ac2f9f7d5d469583c5b0fff9f6 Mon Sep 17 00:00:00 2001 From: ramindani Date: Tue, 7 May 2024 16:42:51 -0700 Subject: [PATCH 377/465] [SF] Use Render rate for Refresh rate When VRR device does not support refresh rate debug indicator callback Use the render rate as the refresh rate on the indicator. Test: Manually tested refresh rate indicator on device that supports the callback and device that doesn't BUG: 326137213 Change-Id: Ifd0d34909b831991d4525f2b5138e71e4a76c4d6 --- services/surfaceflinger/DisplayDevice.cpp | 4 +++- services/surfaceflinger/RefreshRateOverlay.cpp | 18 ++++++------------ services/surfaceflinger/RefreshRateOverlay.h | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 38cf05327f..a57e626224 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -474,7 +474,6 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool sh features |= RefreshRateOverlay::Features::SetByHwc; } - // TODO(b/296636258) Update to use the render rate range in VRR mode. const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange(); mRefreshRateOverlay = RefreshRateOverlay::create(fpsRange, features); if (mRefreshRateOverlay) { @@ -489,6 +488,9 @@ void DisplayDevice::updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, ATRACE_CALL(); if (mRefreshRateOverlay) { if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) { + if (mRefreshRateSelector->isVrrDevice() && !mRefreshRateOverlay->isSetByHwc()) { + refreshRate = renderFps; + } mRefreshRateOverlay->changeRefreshRate(refreshRate, renderFps); } else { mRefreshRateOverlay->changeRenderRate(renderFps); diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index b40f3323bc..9527a997df 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -200,19 +200,13 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> c BufferCache::const_iterator it = mBufferCache.find({refreshRate.getIntValue(), renderFps.getIntValue(), transformHint}); if (it == mBufferCache.end()) { - // HWC minFps is not known by the framework in order - // to consider lower rates we set minFps to 0. - const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue(); const int maxFps = mFpsRange.max.getIntValue(); - // Clamp to the range. The current refreshRate may be outside of this range if the display - // has changed its set of supported refresh rates. - const int displayIntFps = std::clamp(refreshRate.getIntValue(), minFps, maxFps); + // Clamp to supported refresh rate range: the current refresh rate may be outside of this + // range if the display has changed its set of supported refresh rates. + const int refreshIntFps = std::clamp(refreshRate.getIntValue(), 0, maxFps); const int renderIntFps = renderFps.getIntValue(); - - // Ensure non-zero range to avoid division by zero. - const float fpsScale = - static_cast(displayIntFps - minFps) / std::max(1, maxFps - minFps); + const float fpsScale = static_cast(refreshIntFps) / maxFps; constexpr SkColor kMinFpsColor = SK_ColorRED; constexpr SkColor kMaxFpsColor = SK_ColorGREEN; @@ -228,9 +222,9 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> c const SkColor color = colorBase.toSkColor(); - auto buffers = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures); + auto buffers = draw(refreshIntFps, renderIntFps, color, transformHint, mFeatures); it = mBufferCache - .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers)) + .try_emplace({refreshIntFps, renderIntFps, transformHint}, std::move(buffers)) .first; } diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index 93ec36e7f8..b2896f07dd 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -65,7 +65,7 @@ private: using Buffers = std::vector>; - static Buffers draw(int vsyncRate, int renderFps, SkColor, ui::Transform::RotationFlags, + static Buffers draw(int refreshRate, int renderFps, SkColor, ui::Transform::RotationFlags, ftl::Flags); static void drawNumber(int number, int left, SkColor, SkCanvas&); -- GitLab From a7e752e25f54296c907ddbf5a682990bf9e85f6d Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 24 May 2024 18:21:21 +0000 Subject: [PATCH 378/465] Plumb in local tonemapping flag into renderengine The detailed local tonemapping implementation will come in a later CL. Also allow the flag to configure luminance spaces in SF, to prevent SDR-only regions from dimming. Bug: 329464641 Test: builds Change-Id: Ia2bbf12b2cdfb5597137a6028911ff71b3d866d3 --- .../include/renderengine/DisplaySettings.h | 16 ++++++++ libs/renderengine/skia/SkiaRenderEngine.cpp | 5 +++ .../surfaceflinger/ScreenCaptureOutput.cpp | 13 +++++-- services/surfaceflinger/ScreenCaptureOutput.h | 5 ++- services/surfaceflinger/SurfaceFlinger.cpp | 38 ++++++++++++------- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h index deb62534a5..b640983a55 100644 --- a/libs/renderengine/include/renderengine/DisplaySettings.h +++ b/libs/renderengine/include/renderengine/DisplaySettings.h @@ -86,6 +86,22 @@ struct DisplaySettings { // Configures the rendering intent of the output display. This is used for tonemapping. aidl::android::hardware::graphics::composer3::RenderIntent renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC; + + // Tonemapping strategy to use for each layer. This is only used for tonemapping HDR source + // content + enum class TonemapStrategy { + // Use a tonemapper defined by libtonemap. This may be OEM-defined as of Android 13, aka + // undefined. + // This is typically a global tonemapper, designed to match what is on screen. + Libtonemap, + // Use a local tonemapper. Because local tonemapping uses large intermediate allocations, + // this + // method is primarily recommended for infrequent rendering that does not need to exactly + // match + // pixels that are on-screen. + Local, + }; + TonemapStrategy tonemapStrategy = TonemapStrategy::Libtonemap; }; static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) { diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index b973211966..ccbf0924d0 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -519,6 +519,11 @@ sk_sp SkiaRenderEngine::createRuntimeEffectShader( } if (parameters.requiresLinearEffect) { + if (parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local) { + // TODO: Apply a local tonemap + // fallthrough for now + } + auto effect = shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace, .outputDataspace = parameters.outputDataSpace, diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp index dd03366bcc..8bb72b8470 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.cpp +++ b/services/surfaceflinger/ScreenCaptureOutput.cpp @@ -30,7 +30,7 @@ std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutp ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&, const compositionengine::Output::ColorProfile&, bool>(args.compositionEngine, args.renderArea, args.colorProfile, args.regionSampling, - args.dimInGammaSpaceForEnhancedScreenshots); + args.dimInGammaSpaceForEnhancedScreenshots, args.enableLocalTonemapping); output->editState().isSecure = args.renderArea.isSecure(); output->editState().isProtected = args.isProtected; output->setCompositionEnabled(true); @@ -63,11 +63,13 @@ std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutp ScreenCaptureOutput::ScreenCaptureOutput( const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile, - bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots) + bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots, + bool enableLocalTonemapping) : mRenderArea(renderArea), mColorProfile(colorProfile), mRegionSampling(regionSampling), - mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots) {} + mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots), + mEnableLocalTonemapping(enableLocalTonemapping) {} void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) { auto& outputState = editState(); @@ -88,6 +90,11 @@ renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisp aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF; } + if (mEnableLocalTonemapping) { + clientCompositionDisplay.tonemapStrategy = + renderengine::DisplaySettings::TonemapStrategy::Local; + } + return clientCompositionDisplay; } diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h index 069f458bdb..c233ead575 100644 --- a/services/surfaceflinger/ScreenCaptureOutput.h +++ b/services/surfaceflinger/ScreenCaptureOutput.h @@ -39,6 +39,7 @@ struct ScreenCaptureOutputArgs { bool treat170mAsSrgb; bool dimInGammaSpaceForEnhancedScreenshots; bool isProtected = false; + bool enableLocalTonemapping = false; }; // ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer. @@ -49,7 +50,8 @@ class ScreenCaptureOutput : public compositionengine::impl::Output { public: ScreenCaptureOutput(const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile, - bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots); + bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots, + bool enableLocalTonemapping); void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override; @@ -67,6 +69,7 @@ private: const compositionengine::Output::ColorProfile& mColorProfile; const bool mRegionSampling; const bool mDimInGammaSpaceForEnhancedScreenshots; + const bool mEnableLocalTonemapping; }; std::shared_ptr createScreenCaptureOutput(ScreenCaptureOutputArgs); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 66385d8db5..95f37997ef 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8302,6 +8302,9 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( captureResults.capturedDataspace = requestedDataspace; + const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() && + !renderArea->getHintForSeamlessTransition(); + { Mutex::Autolock lock(mStateLock); const DisplayDevice* display = nullptr; @@ -8335,16 +8338,19 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( displayBrightnessNits = sdrWhitePointNits; } else { displayBrightnessNits = state.displayBrightnessNits; - // Only clamp the display brightness if this is not a seamless transition. Otherwise - // for seamless transitions it's important to match the current display state as the - // buffer will be shown under these same conditions, and we want to avoid any - // flickers - if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) { - // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming - // the SDR portion. 2.0 chosen by experimentation - constexpr float kMaxScreenshotHeadroom = 2.0f; - displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, - displayBrightnessNits); + + if (!enableLocalTonemapping) { + // Only clamp the display brightness if this is not a seamless transition. + // Otherwise for seamless transitions it's important to match the current + // display state as the buffer will be shown under these same conditions, and we + // want to avoid any flickers + if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) { + // Restrict the amount of HDR "headroom" in the screenshot to avoid + // over-dimming the SDR portion. 2.0 chosen by experimentation + constexpr float kMaxScreenshotHeadroom = 2.0f; + displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, + displayBrightnessNits); + } } } @@ -8376,7 +8382,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace, sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected, layerFEs = copyLayerFEs(), layerStack, regionSampling, - renderArea = std::move(renderArea), renderIntent]() -> FenceResult { + renderArea = std::move(renderArea), renderIntent, + enableLocalTonemapping]() -> FenceResult { std::unique_ptr compositionEngine = mFactory.createCompositionEngine(); compositionEngine->setRenderEngine(mRenderEngine.get()); @@ -8385,7 +8392,11 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( .renderIntent = renderIntent}; float targetBrightness = 1.0f; - if (dataspace == ui::Dataspace::BT2020_HLG) { + if (enableLocalTonemapping) { + // Boost the whole scene so that SDR white is at 1.0 while still communicating the hdr + // sdr ratio via display brightness / sdrWhite nits. + targetBrightness = sdrWhitePointNits / displayBrightnessNits; + } else if (dataspace == ui::Dataspace::BT2020_HLG) { const float maxBrightnessNits = displayBrightnessNits / sdrWhitePointNits * 203; // With a low dimming ratio, don't fit the entire curve. Otherwise mixed content // will appear way too bright. @@ -8411,7 +8422,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( .treat170mAsSrgb = mTreat170mAsSrgb, .dimInGammaSpaceForEnhancedScreenshots = dimInGammaSpaceForEnhancedScreenshots, - .isProtected = isProtected}); + .isProtected = isProtected, + .enableLocalTonemapping = enableLocalTonemapping}); const float colorSaturation = grayscale ? 0 : 1; compositionengine::CompositionRefreshArgs refreshArgs{ -- GitLab From c9de92a5fe681ea689a876bbeaaec7ff69df2e48 Mon Sep 17 00:00:00 2001 From: Alan Ding Date: Fri, 24 May 2024 00:27:11 -0700 Subject: [PATCH 379/465] Reland "ui: Refactor stable ID generation for GPU virtual displays" This is a partial port of ag/17832464 for main with additional tests. Use ftl::stable_hash to generate stable ID from uniqueId for GPU virtual displays. This allows FLAG_STABLE consistent with PhysicalDisplayId that uses a unique EDID, as well as being stable across reboots when using hardcoded or static unique ID. Add DisplayId.isStable()/isVirtual() interfaces and refactor previous legacy usages. Bug: 339525838 Bug: 137375833 Bug: 194863377 Test: atest libsurfaceflinger_unittest Test: atest libcompositionengine_test Test: atest DisplayIdGeneratorTest Change-Id: I441046f95860bbcaf837468a3c3f5c944225adde --- libs/ui/include/ui/DisplayId.h | 42 +++++++++++------ libs/ui/tests/DisplayId_test.cpp | 47 +++++++++++++++++-- .../CompositionEngine/src/Display.cpp | 2 +- services/surfaceflinger/DisplayDevice.h | 2 +- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h index 3a31fa0848..8a14db8ffa 100644 --- a/libs/ui/include/ui/DisplayId.h +++ b/libs/ui/include/ui/DisplayId.h @@ -20,6 +20,7 @@ #include #include +#include #include namespace android { @@ -38,6 +39,9 @@ struct DisplayId { constexpr DisplayId(const DisplayId&) = default; DisplayId& operator=(const DisplayId&) = default; + constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; } + constexpr bool isStable() const { return value & FLAG_STABLE; } + uint64_t value; // For deserialization. @@ -76,10 +80,10 @@ inline std::ostream& operator<<(std::ostream& stream, DisplayId displayId) { // DisplayId of a physical display, such as the internal display or externally connected display. struct PhysicalDisplayId : DisplayId { static constexpr ftl::Optional tryCast(DisplayId id) { - if (id.value & FLAG_VIRTUAL) { + if (id.isVirtual()) { return std::nullopt; } - return {PhysicalDisplayId(id)}; + return PhysicalDisplayId(id); } // Returns a stable ID based on EDID information. @@ -117,25 +121,23 @@ struct VirtualDisplayId : DisplayId { static constexpr uint64_t FLAG_GPU = 1ULL << 61; static constexpr std::optional tryCast(DisplayId id) { - if (id.value & FLAG_VIRTUAL) { - return {VirtualDisplayId(id)}; + if (id.isVirtual()) { + return VirtualDisplayId(id); } return std::nullopt; } protected: - constexpr VirtualDisplayId(uint64_t flags, BaseId baseId) - : DisplayId(DisplayId::FLAG_VIRTUAL | flags | baseId) {} - + explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {} explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {} }; struct HalVirtualDisplayId : VirtualDisplayId { - explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(0, baseId) {} + explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(baseId) {} static constexpr std::optional tryCast(DisplayId id) { - if ((id.value & FLAG_VIRTUAL) && !(id.value & VirtualDisplayId::FLAG_GPU)) { - return {HalVirtualDisplayId(id)}; + if (id.isVirtual() && !(id.value & FLAG_GPU)) { + return HalVirtualDisplayId(id); } return std::nullopt; } @@ -145,17 +147,27 @@ private: }; struct GpuVirtualDisplayId : VirtualDisplayId { - explicit constexpr GpuVirtualDisplayId(BaseId baseId) - : VirtualDisplayId(VirtualDisplayId::FLAG_GPU, baseId) {} + explicit constexpr GpuVirtualDisplayId(BaseId baseId) : VirtualDisplayId(FLAG_GPU | baseId) {} + + static constexpr std::optional fromUniqueId(const std::string& uniqueId) { + if (const auto hashOpt = ftl::stable_hash(uniqueId)) { + return GpuVirtualDisplayId(HashTag{}, *hashOpt); + } + return {}; + } static constexpr std::optional tryCast(DisplayId id) { - if ((id.value & FLAG_VIRTUAL) && (id.value & VirtualDisplayId::FLAG_GPU)) { - return {GpuVirtualDisplayId(id)}; + if (id.isVirtual() && (id.value & FLAG_GPU)) { + return GpuVirtualDisplayId(id); } return std::nullopt; } private: + struct HashTag {}; // Disambiguate with BaseId constructor. + constexpr GpuVirtualDisplayId(HashTag, uint64_t hash) + : VirtualDisplayId(FLAG_STABLE | FLAG_GPU | hash) {} + explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {} }; @@ -169,7 +181,7 @@ struct HalDisplayId : DisplayId { if (GpuVirtualDisplayId::tryCast(id)) { return std::nullopt; } - return {HalDisplayId(id)}; + return HalDisplayId(id); } private: diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp index 8ddee7e740..ef686dfc83 100644 --- a/libs/ui/tests/DisplayId_test.cpp +++ b/libs/ui/tests/DisplayId_test.cpp @@ -24,7 +24,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) { constexpr uint8_t port = 1; constexpr uint16_t manufacturerId = 13; constexpr uint32_t modelHash = 42; - PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash); + const PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash); EXPECT_EQ(port, id.getPort()); EXPECT_EQ(manufacturerId, id.getManufacturerId()); EXPECT_FALSE(VirtualDisplayId::tryCast(id)); @@ -39,7 +39,7 @@ TEST(DisplayIdTest, createPhysicalIdFromEdid) { TEST(DisplayIdTest, createPhysicalIdFromPort) { constexpr uint8_t port = 3; - PhysicalDisplayId id = PhysicalDisplayId::fromPort(port); + const PhysicalDisplayId id = PhysicalDisplayId::fromPort(port); EXPECT_EQ(port, id.getPort()); EXPECT_FALSE(VirtualDisplayId::tryCast(id)); EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); @@ -52,7 +52,34 @@ TEST(DisplayIdTest, createPhysicalIdFromPort) { } TEST(DisplayIdTest, createGpuVirtualId) { - GpuVirtualDisplayId id(42); + const GpuVirtualDisplayId id(42); + EXPECT_TRUE(VirtualDisplayId::tryCast(id)); + EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(PhysicalDisplayId::tryCast(id)); + EXPECT_FALSE(HalDisplayId::tryCast(id)); + + EXPECT_EQ(id, DisplayId::fromValue(id.value)); + EXPECT_EQ(id, DisplayId::fromValue(id.value)); +} + +TEST(DisplayIdTest, createVirtualIdFromGpuVirtualId) { + const VirtualDisplayId id(GpuVirtualDisplayId(42)); + EXPECT_TRUE(VirtualDisplayId::tryCast(id)); + EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(PhysicalDisplayId::tryCast(id)); + EXPECT_FALSE(HalDisplayId::tryCast(id)); + + const bool isGpuVirtualId = (id.value & VirtualDisplayId::FLAG_GPU); + EXPECT_EQ((id.isVirtual() && isGpuVirtualId), GpuVirtualDisplayId::tryCast(id).has_value()); +} + +TEST(DisplayIdTest, createGpuVirtualIdFromUniqueId) { + static const std::string kUniqueId("virtual:ui:DisplayId_test"); + const auto idOpt = GpuVirtualDisplayId::fromUniqueId(kUniqueId); + ASSERT_TRUE(idOpt.has_value()); + const GpuVirtualDisplayId id = idOpt.value(); EXPECT_TRUE(VirtualDisplayId::tryCast(id)); EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id)); EXPECT_FALSE(HalVirtualDisplayId::tryCast(id)); @@ -64,7 +91,7 @@ TEST(DisplayIdTest, createGpuVirtualId) { } TEST(DisplayIdTest, createHalVirtualId) { - HalVirtualDisplayId id(42); + const HalVirtualDisplayId id(42); EXPECT_TRUE(VirtualDisplayId::tryCast(id)); EXPECT_TRUE(HalVirtualDisplayId::tryCast(id)); EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id)); @@ -75,4 +102,16 @@ TEST(DisplayIdTest, createHalVirtualId) { EXPECT_EQ(id, DisplayId::fromValue(id.value)); } +TEST(DisplayIdTest, createVirtualIdFromHalVirtualId) { + const VirtualDisplayId id(HalVirtualDisplayId(42)); + EXPECT_TRUE(VirtualDisplayId::tryCast(id)); + EXPECT_TRUE(HalVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id)); + EXPECT_FALSE(PhysicalDisplayId::tryCast(id)); + EXPECT_TRUE(HalDisplayId::tryCast(id)); + + const bool isGpuVirtualId = (id.value & VirtualDisplayId::FLAG_GPU); + EXPECT_EQ((id.isVirtual() && !isGpuVirtualId), HalVirtualDisplayId::tryCast(id).has_value()); +} + } // namespace android::ui diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index c18be7a8e8..83b1b68a17 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -79,7 +79,7 @@ void Display::setSecure(bool secure) { } bool Display::isVirtual() const { - return VirtualDisplayId::tryCast(mId).has_value(); + return mId.isVirtual(); } std::optional Display::getDisplayId() const { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index b339bc6c70..a21559fb45 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -89,7 +89,7 @@ public: return mCompositionDisplay; } - bool isVirtual() const { return VirtualDisplayId::tryCast(getId()).has_value(); } + bool isVirtual() const { return getId().isVirtual(); } bool isPrimary() const { return mIsPrimary; } // isSecure indicates whether this display can be trusted to display -- GitLab From f2ea10cb55b7578b7600ae2becb2d2d7070bef3c Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 24 May 2024 19:13:21 +0000 Subject: [PATCH 380/465] Introduce MouriMap MouriMap is a local-tonemapping algorithm optimized for near-exact preservation of SDR/LDR regions, while trying to do a good job of rendering HDR. MouriMap was designed to run well on mobile hardware. On a Pixel 8 Pro, MouriMap is able to tonemap screen-sized images between 20 and 25 milliseconds. This is not fast enough for real-time rendering at the panel refresh rate. But, this is sufficient for screenshots, which is the use-case that MouriMap is intended to be deployed for. Tests will follow after this patch. Bug: 329464641 Test: builds, boots Test: Swipe apps into Recents Test: adb screenshot Change-Id: I0ded29b65ccf41940de74cff26d36275bfa46e78 --- libs/renderengine/Android.bp | 1 + libs/renderengine/skia/SkiaRenderEngine.cpp | 20 ++- libs/renderengine/skia/filters/MouriMap.cpp | 183 ++++++++++++++++++++ libs/renderengine/skia/filters/MouriMap.h | 81 +++++++++ 4 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 libs/renderengine/skia/filters/MouriMap.cpp create mode 100644 libs/renderengine/skia/filters/MouriMap.h diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index c003111ebd..757d935647 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -102,6 +102,7 @@ filegroup { "skia/filters/GaussianBlurFilter.cpp", "skia/filters/KawaseBlurFilter.cpp", "skia/filters/LinearEffect.cpp", + "skia/filters/MouriMap.cpp", "skia/filters/StretchShaderFactory.cpp", ], } diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index ccbf0924d0..d844764a2f 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -79,6 +79,7 @@ #include "filters/GaussianBlurFilter.h" #include "filters/KawaseBlurFilter.h" #include "filters/LinearEffect.h" +#include "filters/MouriMap.h" #include "log/log_main.h" #include "skia/compat/SkiaBackendTexture.h" #include "skia/debug/SkiaCapture.h" @@ -509,9 +510,9 @@ sk_sp SkiaRenderEngine::createRuntimeEffectShader( // Determine later on if we need to leverage the stertch shader within // surface flinger const auto& stretchEffect = parameters.layer.stretchEffect; + const auto& targetBuffer = parameters.layer.source.buffer.buffer; auto shader = parameters.shader; if (stretchEffect.hasEffect()) { - const auto targetBuffer = parameters.layer.source.buffer.buffer; const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr; if (graphicBuffer && parameters.shader) { shader = mStretchShaderFactory.createSkShader(shader, stretchEffect); @@ -519,9 +520,22 @@ sk_sp SkiaRenderEngine::createRuntimeEffectShader( } if (parameters.requiresLinearEffect) { + const auto format = targetBuffer != nullptr + ? std::optional( + static_cast(targetBuffer->getPixelFormat())) + : std::nullopt; + if (parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local) { - // TODO: Apply a local tonemap - // fallthrough for now + // TODO: Handle color matrix transforms in linear space. + SkImage* image = parameters.shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr); + if (image) { + static MouriMap kMapper; + const float ratio = getHdrRenderType(parameters.layer.sourceDataspace, format) == + HdrRenderType::GENERIC_HDR + ? 1.0f + : parameters.layerDimmingRatio; + return kMapper.mouriMap(getActiveContext(), parameters.shader, ratio); + } } auto effect = diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp new file mode 100644 index 0000000000..7d8b8a5116 --- /dev/null +++ b/libs/renderengine/skia/filters/MouriMap.cpp @@ -0,0 +1,183 @@ +/* + * Copyright 2024 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. + */ +#include "MouriMap.h" +#include +#include +#include +#include + +namespace android { +namespace renderengine { +namespace skia { +namespace { +sk_sp makeEffect(const SkString& sksl) { + auto [effect, error] = SkRuntimeEffect::MakeForShader(sksl); + LOG_ALWAYS_FATAL_IF(!effect, "RuntimeShader error: %s", error.c_str()); + return effect; +} +const SkString kCrosstalkAndChunk16x16(R"( + uniform shader bitmap; + uniform float hdrSdrRatio; + vec4 main(vec2 xy) { + float maximum = 0.0; + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + float3 linear = toLinearSrgb(bitmap.eval(xy * 16 + vec2(x, y)).rgb) * hdrSdrRatio; + float maxRGB = max(linear.r, max(linear.g, linear.b)); + maximum = max(maximum, log2(max(maxRGB, 1.0))); + } + } + return float4(float3(maximum), 1.0); + } +)"); +const SkString kChunk8x8(R"( + uniform shader bitmap; + vec4 main(vec2 xy) { + float maximum = 0.0; + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + maximum = max(maximum, bitmap.eval(xy * 8 + vec2(x, y)).r); + } + } + return float4(float3(maximum), 1.0); + } +)"); +const SkString kBlur(R"( + uniform shader bitmap; + vec4 main(vec2 xy) { + float C[5]; + C[0] = 1.0 / 16.0; + C[1] = 4.0 / 16.0; + C[2] = 6.0 / 16.0; + C[3] = 4.0 / 16.0; + C[4] = 1.0 / 16.0; + float result = 0.0; + for (int y = -2; y <= 2; y++) { + for (int x = -2; x <= 2; x++) { + result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r; + } + } + return float4(float3(exp2(result)), 1.0); + } +)"); +const SkString kTonemap(R"( + uniform shader image; + uniform shader lux; + uniform float scaleFactor; + uniform float hdrSdrRatio; + vec4 main(vec2 xy) { + float localMax = lux.eval(xy * scaleFactor).r; + float4 rgba = image.eval(xy); + float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio; + + if (localMax <= 1.0) { + return float4(fromLinearSrgb(linear), 1.0); + } + + float maxRGB = max(linear.r, max(linear.g, linear.b)); + localMax = max(localMax, maxRGB); + float gain = (1 + maxRGB / (localMax * localMax)) / (1 + maxRGB); + return float4(fromLinearSrgb(linear * gain), 1.0); + } +)"); + +// Draws the given runtime shader on a GPU surface and returns the result as an SkImage. +sk_sp makeImage(SkSurface* surface, const SkRuntimeShaderBuilder& builder) { + sk_sp shader = builder.makeShader(nullptr); + LOG_ALWAYS_FATAL_IF(!shader, "%s, Failed to make shader!", __func__); + SkPaint paint; + paint.setShader(std::move(shader)); + paint.setBlendMode(SkBlendMode::kSrc); + surface->getCanvas()->drawPaint(paint); + return surface->makeImageSnapshot(); +} + +} // namespace + +MouriMap::MouriMap() + : mCrosstalkAndChunk16x16(makeEffect(kCrosstalkAndChunk16x16)), + mChunk8x8(makeEffect(kChunk8x8)), + mBlur(makeEffect(kBlur)), + mTonemap(makeEffect(kTonemap)) {} + +sk_sp MouriMap::mouriMap(SkiaGpuContext* context, sk_sp input, + float hdrSdrRatio) { + auto downchunked = downchunk(context, input, hdrSdrRatio); + auto localLux = blur(context, downchunked.get()); + return tonemap(input, localLux.get(), hdrSdrRatio); +} + +sk_sp MouriMap::downchunk(SkiaGpuContext* context, sk_sp input, + float hdrSdrRatio) const { + SkMatrix matrix; + SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr); + SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16); + crosstalkAndChunk16x16Builder.child("bitmap") = input; + crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio; + // TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV + // for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't + // care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR + // content that follows BT. 2408, 25% of the bit range for HLG and 42% of the bit range for PQ + // are reserved for HDR. This means that we can fit the entire HDR range for 10-bit HLG inside + // of 8 bits. We can also fit about half of the range for PQ, but most content does not fill the + // entire 10k nit range for PQ. Furthermore, we blur all of this later on anyways, so we might + // not need to be so precise. So, it's possible that we could use A8 or R8 instead. If we want + // to be really conservative we can try to use R16 or even RGBA1010102 to fake an R10 surface, + // which would cut write bandwidth significantly. + static constexpr auto kFirstDownscaleAmount = 16; + sk_sp firstDownsampledSurface = context->createRenderTarget( + image->imageInfo() + .makeWH(std::max(1, image->width() / kFirstDownscaleAmount), + std::max(1, image->height() / kFirstDownscaleAmount)) + .makeColorType(kRGBA_F16_SkColorType)); + LOG_ALWAYS_FATAL_IF(!firstDownsampledSurface, "%s: Failed to create surface!", __func__); + auto firstDownsampledImage = + makeImage(firstDownsampledSurface.get(), crosstalkAndChunk16x16Builder); + SkRuntimeShaderBuilder chunk8x8Builder(mChunk8x8); + chunk8x8Builder.child("bitmap") = + firstDownsampledImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions()); + static constexpr auto kSecondDownscaleAmount = 8; + sk_sp secondDownsampledSurface = context->createRenderTarget( + firstDownsampledImage->imageInfo() + .makeWH(std::max(1, firstDownsampledImage->width() / kSecondDownscaleAmount), + std::max(1, firstDownsampledImage->height() / kSecondDownscaleAmount))); + LOG_ALWAYS_FATAL_IF(!secondDownsampledSurface, "%s: Failed to create surface!", __func__); + return makeImage(secondDownsampledSurface.get(), chunk8x8Builder); +} +sk_sp MouriMap::blur(SkiaGpuContext* context, SkImage* input) const { + SkRuntimeShaderBuilder blurBuilder(mBlur); + blurBuilder.child("bitmap") = + input->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions()); + sk_sp blurSurface = context->createRenderTarget(input->imageInfo()); + LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__); + return makeImage(blurSurface.get(), blurBuilder); +} +sk_sp MouriMap::tonemap(sk_sp input, SkImage* localLux, + float hdrSdrRatio) const { + static constexpr float kScaleFactor = 1.0f / 128.0f; + SkRuntimeShaderBuilder tonemapBuilder(mTonemap); + tonemapBuilder.child("image") = input; + tonemapBuilder.child("lux") = + localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone)); + tonemapBuilder.uniform("scaleFactor") = kScaleFactor; + tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio; + return tonemapBuilder.makeShader(); +} +} // namespace skia +} // namespace renderengine +} // namespace android \ No newline at end of file diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h new file mode 100644 index 0000000000..3c0df8abf0 --- /dev/null +++ b/libs/renderengine/skia/filters/MouriMap.h @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include +#include +#include +#include "../compat/SkiaGpuContext.h" +namespace android { +namespace renderengine { +namespace skia { +/** + * MouriMap is a fast, albeit not realtime, tonemapping algorithm optimized for near-exact + * preservation of SDR (or, equivalently, LDR) regions, while trying to do an acceptable job of + * preserving HDR detail. + * + * MouriMap is a local tonemapping algorithm, meaning that nearby pixels are taken into + * consideration when choosing a tonemapping curve. + * + * The algorithm conceptually is as follows: + * 1. Partition the image into 128x128 chunks, computing the log2(maximum luminance) in each chunk + *. a. Maximum luminance is computed as max(R, G, B), where the R, G, B values are in linear + *. luminance on a scale defined by the destination color gamut. Max(R, G, B) has been found + *. to minimize difference in hue while restricting to typical LDR color volumes. See: Burke, + *. Adam & Smith, Michael & Zink, Michael. 2020. Color Volume and Hue-preservation in HDR + *. Tone Mapping. SMPTE Motion Imaging Journal. + *. b. Each computed luminance is lower-bounded by 1.0 in Skia's color + *. management, or 203 nits. + * 2. Blur the resulting chunks using a 5x5 gaussian kernel, to smooth out the local luminance map. + * 3. Now, for each pixel in the original image: + * a. Upsample from the blurred chunks of luminance computed in (2). Call this luminance value + *. L: an estimate of the maximum luminance of surrounding pixels. + *. b. If the luminance is less than 1.0 (203 nits), then do not modify the pixel value of the + *. original image. + *. c. Otherwise, + *. parameterize a tone-mapping curve using a method described by Chrome: + *. https://docs.google.com/document/d/17T2ek1i2R7tXdfHCnM-i5n6__RoYe0JyMfKmTEjoGR8/. + *. i. Compute a gain G = (1 + max(linear R, linear G, linear B) / (L * L)) + *. / (1 + max(linear R, linear G, linear B)). Note the similarity with the 1D curve + *. described by Erik Reinhard, Michael Stark, Peter Shirley, and James Ferwerda. 2002. + *. Photographic tone reproduction for digital images. ACM Trans. Graph. + *. ii. Multiply G by the linear source colors to compute the final colors. + * + * Because it is a multi-renderpass algorithm requiring multiple off-screen textures, MouriMap is + * typically not suitable to be ran "frequently", at high refresh rates (e.g., 120hz). However, + * MouriMap is sufficiently fast enough for infrequent composition where preserving SDR detail is + * most important, such as for screenshots. + */ +class MouriMap { +public: + MouriMap(); + // Apply the MouriMap tonemmaping operator to the input. + // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger + // then 1.0 means that there is headroom above the SDR region. + sk_sp mouriMap(SkiaGpuContext* context, sk_sp input, float hdrSdrRatio); + +private: + sk_sp downchunk(SkiaGpuContext* context, sk_sp input, + float hdrSdrRatio) const; + sk_sp blur(SkiaGpuContext* context, SkImage* input) const; + sk_sp tonemap(sk_sp input, SkImage* localLux, float hdrSdrRatio) const; + const sk_sp mCrosstalkAndChunk16x16; + const sk_sp mChunk8x8; + const sk_sp mBlur; + const sk_sp mTonemap; +}; +} // namespace skia +} // namespace renderengine +} // namespace android \ No newline at end of file -- GitLab From 5697ad6f5466d02680da789d7110cd11c179f55d Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Fri, 24 May 2024 20:00:52 +0000 Subject: [PATCH 381/465] Remove extra check for brightening layers I8de3a05ec245788a5aa0b99d28aa36a678d12cbc missed this. Bug: 329464641 Test: adb screencap Change-Id: I83d2898ce7578d3de4ca4a6c682e04ca4daaf8b4 --- libs/renderengine/skia/SkiaRenderEngine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp index d3441643f6..b9016015a1 100644 --- a/libs/renderengine/skia/SkiaRenderEngine.cpp +++ b/libs/renderengine/skia/SkiaRenderEngine.cpp @@ -712,8 +712,7 @@ void SkiaRenderEngine::drawLayersInternal( [&](const auto& l) { return l.whitePointNits; }); // ...and compute the dimming ratio if dimming is requested - const float displayDimmingRatio = display.targetLuminanceNits > 0.f && - maxLayerWhitePoint > 0.f && display.targetLuminanceNits > maxLayerWhitePoint + const float displayDimmingRatio = display.targetLuminanceNits > 0.f && maxLayerWhitePoint > 0.f ? maxLayerWhitePoint / display.targetLuminanceNits : 1.f; -- GitLab From bb2ec6a4a9cbc4f39819723ba48be404a5443296 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Mon, 27 May 2024 22:18:05 +0000 Subject: [PATCH 382/465] Log detected cycles in LayerHierarchy#traverse Prevents a stack overflow in the event of a cyclic hierarchy. Fixes: b/341182047 Test: atest SurfaceFlinger_test Change-Id: I930cda6258cdabe0bdc98bd68bdd88f1de7c1a8c --- services/surfaceflinger/FrontEnd/LayerHierarchy.cpp | 10 +++++++--- services/surfaceflinger/FrontEnd/LayerHierarchy.h | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index 0dcbb3c78d..2b20de38d7 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -52,8 +52,12 @@ LayerHierarchy::LayerHierarchy(const LayerHierarchy& hierarchy, bool childrenOnl mChildren = hierarchy.mChildren; } -void LayerHierarchy::traverse(const Visitor& visitor, - LayerHierarchy::TraversalPath& traversalPath) const { +void LayerHierarchy::traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& traversalPath, + uint32_t depth) const { + LLOG_ALWAYS_FATAL_WITH_TRACE_IF(depth > 50, + "Cycle detected in LayerHierarchy::traverse. See " + "traverse_stack_overflow_transactions.winscope"); + if (mLayer) { bool breakTraversal = !visitor(*this, traversalPath); if (breakTraversal) { @@ -66,7 +70,7 @@ void LayerHierarchy::traverse(const Visitor& visitor, for (auto& [child, childVariant] : mChildren) { ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id, childVariant); - child->traverse(visitor, traversalPath); + child->traverse(visitor, traversalPath, depth + 1); } } diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index f62e7587d5..69710be8df 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -147,7 +147,7 @@ public: if (mLayer) { root.id = mLayer->id; } - traverse(visitor, root); + traverse(visitor, root, /*depth=*/0); } // Traverse the hierarchy in z-order, skipping children that have relative parents. @@ -190,7 +190,8 @@ private: void sortChildrenByZOrder(); void updateChild(LayerHierarchy*, LayerHierarchy::Variant); void traverseInZOrder(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const; - void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const; + void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent, + uint32_t depth = 0) const; void dump(std::ostream& out, const std::string& prefix, LayerHierarchy::Variant variant, bool isLastChild, bool includeMirroredHierarchy) const; -- GitLab From c5545d526c14d413bee3eef8954397e245fdeafe Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 22 May 2024 17:28:49 -0700 Subject: [PATCH 383/465] Allow latch unsignaled if there are no flag changes Instead of checking the change flags, check if the value of the flags changed instead. Change-Id: I0999609666ff78a3af8ba55c88835a9a5a74e8bc Test: presubmit Fixes: 340362109 --- .../FrontEnd/RequestedLayerState.cpp | 24 ++++++------ .../unittests/LayerLifecycleManagerTest.cpp | 37 +++++++++++++++++++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp index 5631facdae..db515e1f2b 100644 --- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp +++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp @@ -588,23 +588,22 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged | - layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged | - layer_state_t::eLayerStackChanged | layer_state_t::eReparent | + layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged | + layer_state_t::eReparent | (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() ? 0 - : layer_state_t::eAutoRefreshChanged); + : (layer_state_t::eAutoRefreshChanged | layer_state_t::eFlagsChanged)); if (s.what & deniedFlags) { ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__, s.what & deniedFlags); return false; } - bool changedFlags = diff(s); - static constexpr auto deniedChanges = layer_state_t::ePositionChanged | - layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged | - layer_state_t::eBackgroundColorChanged | layer_state_t::eMatrixChanged | - layer_state_t::eCornerRadiusChanged | layer_state_t::eBackgroundBlurRadiusChanged | - layer_state_t::eBufferTransformChanged | + const uint64_t changedFlags = diff(s); + const uint64_t deniedChanges = layer_state_t::ePositionChanged | layer_state_t::eAlphaChanged | + layer_state_t::eColorTransformChanged | layer_state_t::eBackgroundColorChanged | + layer_state_t::eMatrixChanged | layer_state_t::eCornerRadiusChanged | + layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBufferTransformChanged | layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged | layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged | layer_state_t::eSidebandStreamChanged | layer_state_t::eColorSpaceAgnosticChanged | @@ -612,10 +611,13 @@ bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const { layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged | layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged | layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged | - layer_state_t::eDesiredHdrHeadroomChanged; + layer_state_t::eDesiredHdrHeadroomChanged | + (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed() + ? layer_state_t::eFlagsChanged + : 0); if (changedFlags & deniedChanges) { ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__, - s.what & deniedChanges); + changedFlags & deniedChanges); return false; } diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index cfc8e99f49..97bd79fcee 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -592,4 +592,41 @@ TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) { mLifecycleManager.commitChanges(); } +TEST_F(LayerLifecycleManagerTest, isSimpleBufferUpdate) { + auto layer = rootLayer(1); + + // no buffer changes + EXPECT_FALSE(layer->isSimpleBufferUpdate({})); + + { + layer_state_t state; + state.what = layer_state_t::eBufferChanged; + EXPECT_TRUE(layer->isSimpleBufferUpdate(state)); + } + + { + layer_state_t state; + state.what = layer_state_t::eReparent | layer_state_t::eBufferChanged; + EXPECT_FALSE(layer->isSimpleBufferUpdate(state)); + } + + { + layer_state_t state; + state.what = layer_state_t::ePositionChanged | layer_state_t::eBufferChanged; + state.x = 9; + state.y = 10; + EXPECT_FALSE(layer->isSimpleBufferUpdate(state)); + } + + { + layer->x = 9; + layer->y = 10; + layer_state_t state; + state.what = layer_state_t::ePositionChanged | layer_state_t::eBufferChanged; + state.x = 9; + state.y = 10; + EXPECT_TRUE(layer->isSimpleBufferUpdate(state)); + } +} + } // namespace android::surfaceflinger::frontend -- GitLab From 39f510fb536d26247997141bdfdcc7d8af890514 Mon Sep 17 00:00:00 2001 From: Nergi Rahardi Date: Thu, 23 May 2024 15:16:54 +0900 Subject: [PATCH 384/465] Pass dequeue timestamp along with buffer data Avoids performance penalties associated with metadata. Bug: 342174822 Test: atest libsurfaceflinger_unittest Test: Manually verified dequeueTime sent without triggering metadata change Change-Id: Ifed747818ea252b2551780ffcefd3309eb41edbe --- libs/gui/BLASTBufferQueue.cpp | 24 +++++++++---------- libs/gui/LayerState.cpp | 2 ++ libs/gui/SurfaceComposerClient.cpp | 3 ++- libs/gui/include/gui/LayerState.h | 2 ++ libs/gui/include/gui/SurfaceComposerClient.h | 3 ++- services/surfaceflinger/Layer.cpp | 8 +++---- services/surfaceflinger/Layer.h | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 13 ++-------- .../Tracing/TransactionProtoParser.cpp | 1 + .../unittests/TransactionFrameTracerTest.cpp | 2 +- .../unittests/TransactionSurfaceFrameTest.cpp | 24 +++++++++---------- 11 files changed, 41 insertions(+), 43 deletions(-) diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp index f317a2eea0..739c3c2a41 100644 --- a/libs/gui/BLASTBufferQueue.cpp +++ b/libs/gui/BLASTBufferQueue.cpp @@ -613,8 +613,19 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( std::bind(releaseBufferCallbackThunk, wp(this) /* callbackContext */, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); sp fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE; + + nsecs_t dequeueTime = -1; + { + std::lock_guard _lock{mTimestampMutex}; + auto dequeueTimeIt = mDequeueTimestamps.find(buffer->getId()); + if (dequeueTimeIt != mDequeueTimestamps.end()) { + dequeueTime = dequeueTimeIt->second; + mDequeueTimestamps.erase(dequeueTimeIt); + } + } + t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId, - releaseBufferCallback); + releaseBufferCallback, dequeueTime); t->setDataspace(mSurfaceControl, static_cast(bufferItem.mDataSpace)); t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata); t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage); @@ -658,17 +669,6 @@ status_t BLASTBufferQueue::acquireNextBufferLocked( mPendingFrameTimelines.pop(); } - { - std::lock_guard _lock{mTimestampMutex}; - auto dequeueTime = mDequeueTimestamps.find(buffer->getId()); - if (dequeueTime != mDequeueTimestamps.end()) { - Parcel p; - p.writeInt64(dequeueTime->second); - t->setMetadata(mSurfaceControl, gui::METADATA_DEQUEUE_TIME, p); - mDequeueTimestamps.erase(dequeueTime); - } - } - mergePendingTransactions(t, bufferItem.mFrameNumber); if (applyTransaction) { // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp index c82bde9a83..3745805aa3 100644 --- a/libs/gui/LayerState.cpp +++ b/libs/gui/LayerState.cpp @@ -985,6 +985,7 @@ status_t BufferData::writeToParcel(Parcel* output) const { SAFE_PARCEL(output->writeBool, hasBarrier); SAFE_PARCEL(output->writeUint64, barrierFrameNumber); SAFE_PARCEL(output->writeUint32, producerId); + SAFE_PARCEL(output->writeInt64, dequeueTime); return NO_ERROR; } @@ -1024,6 +1025,7 @@ status_t BufferData::readFromParcel(const Parcel* input) { SAFE_PARCEL(input->readBool, &hasBarrier); SAFE_PARCEL(input->readUint64, &barrierFrameNumber); SAFE_PARCEL(input->readUint32, &producerId); + SAFE_PARCEL(input->readInt64, &dequeueTime); return NO_ERROR; } diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp index b420aabb84..af91bb3ae2 100644 --- a/libs/gui/SurfaceComposerClient.cpp +++ b/libs/gui/SurfaceComposerClient.cpp @@ -1702,7 +1702,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer( const sp& sc, const sp& buffer, const std::optional>& fence, const std::optional& optFrameNumber, - uint32_t producerId, ReleaseBufferCallback callback) { + uint32_t producerId, ReleaseBufferCallback callback, nsecs_t dequeueTime) { layer_state_t* s = getLayerState(sc); if (!s) { mStatus = BAD_INDEX; @@ -1718,6 +1718,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe bufferData->frameNumber = frameNumber; bufferData->producerId = producerId; bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged; + bufferData->dequeueTime = dequeueTime; if (fence) { bufferData->acquireFence = *fence; bufferData->flags |= BufferData::BufferDataChange::fenceChanged; diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 82889efe36..ba06101059 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -128,6 +128,8 @@ public: client_cache_t cachedBuffer; + nsecs_t dequeueTime; + // Generates the release callback id based on the buffer id and frame number. // This is used as an identifier when release callbacks are invoked. ReleaseCallbackId generateReleaseCallbackId() const; diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h index 9712907396..0862e03c44 100644 --- a/libs/gui/include/gui/SurfaceComposerClient.h +++ b/libs/gui/include/gui/SurfaceComposerClient.h @@ -568,7 +568,8 @@ public: Transaction& setBuffer(const sp& sc, const sp& buffer, const std::optional>& fence = std::nullopt, const std::optional& frameNumber = std::nullopt, - uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr); + uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr, + nsecs_t dequeueTime = -1); Transaction& unsetBuffer(const sp& sc); std::shared_ptr getAndClearBuffer(const sp& sc); diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 6b97e2f799..d27bfd29ef 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -3149,8 +3149,7 @@ void Layer::resetDrawingStateBufferInfo() { bool Layer::setBuffer(std::shared_ptr& buffer, const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime, - bool isAutoTimestamp, std::optional dequeueTime, - const FrameTimelineInfo& info) { + bool isAutoTimestamp, const FrameTimelineInfo& info) { ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false")); const bool frameNumberChanged = @@ -3224,10 +3223,11 @@ bool Layer::setBuffer(std::shared_ptr& buffer, setFrameTimelineVsyncForBufferTransaction(info, postTime); - if (dequeueTime && *dequeueTime != 0) { + if (bufferData.dequeueTime > 0) { const uint64_t bufferId = mDrawingState.buffer->getId(); mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str()); - mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime, + mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, + bufferData.dequeueTime, FrameTracer::FrameEvent::DEQUEUE); mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime, FrameTracer::FrameEvent::QUEUE); diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 9db7664aa2..b9fcd5c333 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -312,7 +312,7 @@ public: bool setBuffer(std::shared_ptr& /* buffer */, const BufferData& /* bufferData */, nsecs_t /* postTime */, nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/, - std::optional /* dequeueTime */, const FrameTimelineInfo& /*info*/); + const FrameTimelineInfo& /*info*/); void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/); bool setDataspace(ui::Dataspace /*dataspace*/); bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f81cd45cc..c0faf16927 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -5618,10 +5618,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime layer->setInputInfo(*s.windowInfoHandle->getInfo()); flags |= eTraversalNeeded; } - std::optional dequeueBufferTimestamp; if (what & layer_state_t::eMetadataChanged) { - dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); - if (const int32_t gameMode = s.metadata.getInt32(gui::METADATA_GAME_MODE, -1); gameMode != -1) { // The transaction will be received on the Task layer and needs to be applied to all @@ -5763,8 +5760,7 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime if (what & layer_state_t::eBufferChanged) { if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, - desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, - frameTimelineInfo)) { + desiredPresentTime, isAutoTimestamp, frameTimelineInfo)) { flags |= eTraversalNeeded; } } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) { @@ -5850,10 +5846,6 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f if (what & layer_state_t::eProducerDisconnect) { layer->onDisconnect(); } - std::optional dequeueBufferTimestamp; - if (what & layer_state_t::eMetadataChanged) { - dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME); - } std::vector> callbackHandles; if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) { @@ -5900,8 +5892,7 @@ uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& f } layer->setTransformHint(transformHint); if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime, - desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp, - frameTimelineInfo)) { + desiredPresentTime, isAutoTimestamp, frameTimelineInfo)) { flags |= eTraversalNeeded; } mLayersWithQueuedFrames.emplace(layer); diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp index fc496b2982..0bafb71f3f 100644 --- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp +++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp @@ -436,6 +436,7 @@ void TransactionProtoParser::fromProto(const perfetto::protos::LayerState& proto layer.bufferData->flags = ftl::Flags(bufferProto.flags()); layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id(); layer.bufferData->acquireFence = Fence::NO_FENCE; + layer.bufferData->dequeueTime = -1; } if (proto.what() & layer_state_t::eApiChanged) { diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp index d4d5b32341..85b61f8fb9 100644 --- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp @@ -94,7 +94,7 @@ public: HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false, - dequeueTime, FrameTimelineInfo{}); + FrameTimelineInfo{}); commitTransaction(layer.get()); nsecs_t latchTime = 25; diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp index 5046a86304..46733b9a83 100644 --- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp +++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp @@ -99,7 +99,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo); acquireFence->signalForTest(12); commitTransaction(layer.get()); @@ -134,7 +134,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -151,7 +151,7 @@ public: 2ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfo); nsecs_t end = systemTime(); acquireFence2->signalForTest(12); @@ -197,7 +197,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo); acquireFence->signalForTest(12); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); @@ -232,7 +232,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -275,7 +275,7 @@ public: FrameTimelineInfo ftInfo3; ftInfo3.vsyncId = 3; ftInfo3.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo3); EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX; @@ -320,7 +320,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX; @@ -335,7 +335,7 @@ public: 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888, 0ULL /*usage*/); - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfo); acquireFence2->signalForTest(12); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -372,7 +372,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX; @@ -392,7 +392,7 @@ public: FrameTimelineInfo ftInfoInv; ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID; ftInfoInv.inputEventId = 0; - layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv); + layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfoInv); auto dropEndTime1 = systemTime(); EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size()); ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX); @@ -413,7 +413,7 @@ public: FrameTimelineInfo ftInfo2; ftInfo2.vsyncId = 2; ftInfo2.inputEventId = 0; - layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2); + layer->setBuffer(externalTexture3, bufferData, 10, 20, false, ftInfo2); auto dropEndTime2 = systemTime(); acquireFence3->signalForTest(12); @@ -462,7 +462,7 @@ public: FrameTimelineInfo ftInfo; ftInfo.vsyncId = 1; ftInfo.inputEventId = 0; - layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo); + layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo); FrameTimelineInfo ftInfo2; ftInfo2.vsyncId = 2; ftInfo2.inputEventId = 0; -- GitLab From cf038cfb43187cadf4114f0ff1b886ecb78ee26a Mon Sep 17 00:00:00 2001 From: Bryan Yu Date: Wed, 8 May 2024 09:25:25 +0000 Subject: [PATCH 385/465] Initiate anr_data_ for dumpstate limited only mode When running in LimitedOnly mode, `anr_data_` was empty. Hence no ANR traces were uploaded in the feedback reports. Test reports uploaded: http://listnr/product/208/report/91736461072 http://listnr/product/208/report/91736451197 Bug: 298145042 Test: Submit test reports to Listnr and verify the VM TRACES AT LAST ANR session exist Change-Id: I53981a73bfdb15e76007eb81567fe0fb5d00ee02 --- cmds/dumpstate/dumpstate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index 6b9a0a0e7e..1f5da39528 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -1569,6 +1569,7 @@ static void DumpstateLimitedOnly() { printf("== ANR Traces\n"); printf("========================================================\n"); + ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX); AddAnrTraceFiles(); printf("========================================================\n"); -- GitLab From 3b468eca28c385ea61832b96dc5387bcaa32b25e Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 27 Mar 2024 22:20:24 -0700 Subject: [PATCH 386/465] Fix sync issue with handling display state changes We may miss some state changes if a display state change comes between processDisplayChangesLocked and commitTransactions. Fix this by grabbing the state lock for the duration of display updates in commit. Test: steps in bug Bug: 330105711, 330103914, 328539539 Merged-In: I4798961551f78d75c45ead6dea5ebca895e5ef7d Change-Id: I4798961551f78d75c45ead6dea5ebca895e5ef7d (cherry picked from commit 878911f7df21c700ddbe9e9c9d28cd0a1776946f) --- services/surfaceflinger/SurfaceFlinger.cpp | 31 +++++++++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 7 ++--- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index cf5f55d7bd..c1f362ccd1 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -2256,7 +2256,7 @@ bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTi outTransactionsAreEmpty = !needsTraversal; const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal; if (shouldCommit) { - commitTransactions(); + commitTransactionsLegacy(); } bool mustComposite = latchBuffers() || shouldCommit; @@ -2380,8 +2380,14 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, mLayerHierarchyBuilder.update(mLayerLifecycleManager); } + // Keep a copy of the drawing state (that is going to be overwritten + // by commitTransactionsLocked) outside of mStateLock so that the side + // effects of the State assignment don't happen with mStateLock held, + // which can cause deadlocks. + State drawingState(mDrawingState); + Mutex::Autolock lock(mStateLock); bool mustComposite = false; - mustComposite |= applyAndCommitDisplayTransactionStates(update.transactions); + mustComposite |= applyAndCommitDisplayTransactionStatesLocked(update.transactions); { ATRACE_NAME("LayerSnapshotBuilder:update"); @@ -2420,7 +2426,7 @@ bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool newDataLatched = false; if (!mLegacyFrontEndEnabled) { ATRACE_NAME("DisplayCallbackAndStatsUpdates"); - mustComposite |= applyTransactions(update.transactions, vsyncId); + mustComposite |= applyTransactionsLocked(update.transactions, vsyncId); traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); }); const nsecs_t latchTime = systemTime(); bool unused = false; @@ -3257,6 +3263,19 @@ void SurfaceFlinger::computeLayerBounds() { void SurfaceFlinger::commitTransactions() { ATRACE_CALL(); + mDebugInTransaction = systemTime(); + + // Here we're guaranteed that some transaction flags are set + // so we can call commitTransactionsLocked unconditionally. + // We clear the flags with mStateLock held to guarantee that + // mCurrentState won't change until the transaction is committed. + mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit); + commitTransactionsLocked(clearTransactionFlags(eTransactionMask)); + mDebugInTransaction = 0; +} + +void SurfaceFlinger::commitTransactionsLegacy() { + ATRACE_CALL(); // Keep a copy of the drawing state (that is going to be overwritten // by commitTransactionsLocked) outside of mStateLock so that the side @@ -5219,9 +5238,8 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin return needsTraversal; } -bool SurfaceFlinger::applyAndCommitDisplayTransactionStates( +bool SurfaceFlinger::applyAndCommitDisplayTransactionStatesLocked( std::vector& transactions) { - Mutex::Autolock lock(mStateLock); bool needsTraversal = false; uint32_t transactionFlags = 0; for (auto& transaction : transactions) { @@ -6009,7 +6027,8 @@ void SurfaceFlinger::initializeDisplays() { if (mLegacyFrontEndEnabled) { applyTransactions(transactions, VsyncId{0}); } else { - applyAndCommitDisplayTransactionStates(transactions); + Mutex::Autolock lock(mStateLock); + applyAndCommitDisplayTransactionStatesLocked(transactions); } { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 678be54c41..afc76471f4 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -750,7 +750,8 @@ private: bool force = false) REQUIRES(mStateLock, kMainThreadContext); - void commitTransactions() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + void commitTransactionsLegacy() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext); + void commitTransactions() REQUIRES(kMainThreadContext, mStateLock); void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock, kMainThreadContext); void doCommitTransactions() REQUIRES(mStateLock); @@ -800,8 +801,8 @@ private: bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext); bool applyTransactions(std::vector&, VsyncId) REQUIRES(kMainThreadContext); - bool applyAndCommitDisplayTransactionStates(std::vector& transactions) - REQUIRES(kMainThreadContext); + bool applyAndCommitDisplayTransactionStatesLocked(std::vector& transactions) + REQUIRES(kMainThreadContext, mStateLock); // Returns true if there is at least one transaction that needs to be flushed bool transactionFlushNeeded(); -- GitLab From 420d074de595faab93f711b3d7f411f13ba15a30 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 4 Apr 2024 11:54:20 +0000 Subject: [PATCH 387/465] Hide Mouse and stylus pointers on mirrored displays Hide the Mouse and Stylus pointers on mirrored displays if a privacy sensitive window is present on the source display. Test: atest PointerChoreographerTest Bug: 325252005 Change-Id: Idc7a73508e360dfc6fc0bd66c4eb21f497e5bf6b --- .../inputflinger/PointerChoreographer.cpp | 138 ++++++++++++------ services/inputflinger/PointerChoreographer.h | 20 ++- .../include/PointerControllerInterface.h | 9 +- .../tests/FakePointerController.cpp | 14 +- .../tests/FakePointerController.h | 3 +- .../tests/PointerChoreographer_test.cpp | 111 +++++++------- 6 files changed, 179 insertions(+), 116 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 8a1eed603f..02453ef7a0 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -89,6 +89,19 @@ void setIconForController(const std::variant, Pointe } } +// filters and returns a set of privacy sensitive displays that are currently visible. +std::unordered_set getPrivacySensitiveDisplaysFromWindowInfos( + const std::vector& windowInfos) { + std::unordered_set privacySensitiveDisplays; + for (const auto& windowInfo : windowInfos) { + if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) && + windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) { + privacySensitiveDisplays.insert(windowInfo.displayId); + } + } + return privacySensitiveDisplays; +} + } // namespace // --- PointerChoreographer --- @@ -246,10 +259,13 @@ void PointerChoreographer::processDrawingTabletEventLocked(const android::Notify } // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist. - auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId, - getMouseControllerConstructor( - args.displayId)); - // TODO (b/325252005): Add handing for drawing tablets mouse pointer controller + auto [it, controllerAdded] = + mDrawingTabletPointersByDevice.try_emplace(args.deviceId, + getMouseControllerConstructor( + args.displayId)); + if (controllerAdded) { + onControllerAddedOrRemovedLocked(); + } PointerControllerInterface& pc = *it->second; @@ -290,7 +306,7 @@ void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMo auto [it, controllerAdded] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor); if (controllerAdded) { - onControllerAddedOrRemoved(); + onControllerAddedOrRemovedLocked(); } PointerControllerInterface& pc = *it->second; @@ -326,10 +342,12 @@ void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& } // Get the stylus pointer controller for the device, or create one if it doesn't exist. - auto [it, _] = + auto [it, controllerAdded] = mStylusPointersByDevice.try_emplace(args.deviceId, getStylusControllerConstructor(args.displayId)); - // TODO (b/325252005): Add handing for stylus pointer controller + if (controllerAdded) { + onControllerAddedOrRemovedLocked(); + } PointerControllerInterface& pc = *it->second; @@ -369,15 +387,15 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) mTouchPointersByDevice.erase(args.deviceId); mStylusPointersByDevice.erase(args.deviceId); mDrawingTabletPointersByDevice.erase(args.deviceId); - onControllerAddedOrRemoved(); + onControllerAddedOrRemovedLocked(); } -void PointerChoreographer::onControllerAddedOrRemoved() { +void PointerChoreographer::onControllerAddedOrRemovedLocked() { if (!HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS) { return; } - bool requireListener = !mTouchPointersByDevice.empty(); - // TODO (b/325252005): Update for other types of pointer controllers + bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() || + !mDrawingTabletPointersByDevice.empty() || !mStylusPointersByDevice.empty(); if (requireListener && mWindowInfoListener == nullptr) { mWindowInfoListener = sp::make(this); @@ -387,12 +405,47 @@ void PointerChoreographer::onControllerAddedOrRemoved() { SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener, &initialInfo); #endif - onWindowInfosChangedLocked(initialInfo.first); + mWindowInfoListener->setInitialDisplayInfos(initialInfo.first); + onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays()); } else if (!requireListener && mWindowInfoListener != nullptr) { #if defined(__ANDROID__) SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener); #endif mWindowInfoListener = nullptr; + } else if (requireListener && mWindowInfoListener != nullptr) { + // controller may have been added to an existing privacy sensitive display, we need to + // update all controllers again + onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays()); + } +} + +void PointerChoreographer::onPrivacySensitiveDisplaysChangedLocked( + const std::unordered_set& privacySensitiveDisplays) { + for (auto& [_, pc] : mTouchPointersByDevice) { + pc->clearSkipScreenshotFlags(); + for (auto displayId : privacySensitiveDisplays) { + pc->setSkipScreenshotFlagForDisplay(displayId); + } + } + + for (auto& [displayId, pc] : mMousePointersByDisplay) { + if (privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()) { + pc->setSkipScreenshotFlagForDisplay(displayId); + } else { + pc->clearSkipScreenshotFlags(); + } + } + + for (auto* pointerControllerByDevice : + {&mDrawingTabletPointersByDevice, &mStylusPointersByDevice}) { + for (auto& [_, pc] : *pointerControllerByDevice) { + auto displayId = pc->getDisplayId(); + if (privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()) { + pc->setSkipScreenshotFlagForDisplay(displayId); + } else { + pc->clearSkipScreenshotFlags(); + } + } } } @@ -407,10 +460,10 @@ void PointerChoreographer::notifyPointerCaptureChanged( mNextListener.notify(args); } -void PointerChoreographer::onWindowInfosChanged( - const std::vector& windowInfos) { +void PointerChoreographer::onPrivacySensitiveDisplaysChanged( + const std::unordered_set& privacySensitiveDisplays) { std::scoped_lock _l(mLock); - onWindowInfosChangedLocked(windowInfos); + onPrivacySensitiveDisplaysChangedLocked(privacySensitiveDisplays); } void PointerChoreographer::dump(std::string& dump) { @@ -467,7 +520,7 @@ PointerChoreographer::ensureMouseControllerLocked(ui::LogicalDisplayId associate if (it == mMousePointersByDisplay.end()) { it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId)) .first; - // TODO (b/325252005): Add handing for mouse pointer controller + onControllerAddedOrRemovedLocked(); } return {displayId, *it->second}; @@ -509,7 +562,9 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo auto [mousePointerIt, isNewMousePointer] = mMousePointersByDisplay.try_emplace(displayId, getMouseControllerConstructor(displayId)); - // TODO (b/325252005): Add handing for mouse pointer controller + if (isNewMousePointer) { + onControllerAddedOrRemovedLocked(); + } mMouseDevices.emplace(info.getId()); if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) { @@ -549,7 +604,7 @@ PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerCo mInputDeviceInfos.end(); }); - onControllerAddedOrRemoved(); + onControllerAddedOrRemovedLocked(); // Check if we need to notify the policy if there's a change on the pointer display ID. return calculatePointerDisplayChangeToNotify(); @@ -715,31 +770,6 @@ bool PointerChoreographer::setPointerIcon( return false; } -void PointerChoreographer::onWindowInfosChangedLocked( - const std::vector& windowInfos) { - // Mark all spot controllers secure on displays containing secure windows and - // remove secure flag from others if required - std::unordered_set privacySensitiveDisplays; - std::unordered_set allDisplayIds; - for (const auto& windowInfo : windowInfos) { - allDisplayIds.insert(windowInfo.displayId); - if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) && - windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) { - privacySensitiveDisplays.insert(windowInfo.displayId); - } - } - - for (auto& it : mTouchPointersByDevice) { - auto& pc = it.second; - for (ui::LogicalDisplayId displayId : allDisplayIds) { - pc->setSkipScreenshot(displayId, - privacySensitiveDisplays.find(displayId) != - privacySensitiveDisplays.end()); - } - } - // TODO (b/325252005): update skip screenshot flag for other types of pointer controllers -} - void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) { std::scoped_lock lock(mLock); if (visible) { @@ -793,11 +823,29 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusContr void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged( const gui::WindowInfosUpdate& windowInfosUpdate) { std::scoped_lock _l(mListenerLock); - if (mPointerChoreographer != nullptr) { - mPointerChoreographer->onWindowInfosChanged(windowInfosUpdate.windowInfos); + if (mPointerChoreographer == nullptr) { + return; + } + auto newPrivacySensitiveDisplays = + getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos); + if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) { + mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays); + mPointerChoreographer->onPrivacySensitiveDisplaysChanged(mPrivacySensitiveDisplays); } } +void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfos( + const std::vector& windowInfos) { + std::scoped_lock _l(mListenerLock); + mPrivacySensitiveDisplays = getPrivacySensitiveDisplaysFromWindowInfos(windowInfos); +} + +std::unordered_set +PointerChoreographer::PointerChoreographerDisplayInfoListener::getPrivacySensitiveDisplays() { + std::scoped_lock _l(mListenerLock); + return mPrivacySensitiveDisplays; +} + void PointerChoreographer::PointerChoreographerDisplayInfoListener:: onPointerChoreographerDestroyed() { std::scoped_lock _l(mListenerLock); diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index 12316c0427..11c5a0c6c7 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace android { @@ -108,7 +109,8 @@ public: void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; // Public because it's also used by tests to simulate the WindowInfosListener callback - void onWindowInfosChanged(const std::vector& windowInfos); + void onPrivacySensitiveDisplaysChanged( + const std::unordered_set& privacySensitiveDisplays); void dump(std::string& dump) override; @@ -133,20 +135,32 @@ private: void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); void processDeviceReset(const NotifyDeviceResetArgs& args); - void onControllerAddedOrRemoved() REQUIRES(mLock); - void onWindowInfosChangedLocked(const std::vector& windowInfos) + void onControllerAddedOrRemovedLocked() REQUIRES(mLock); + void onPrivacySensitiveDisplaysChangedLocked( + const std::unordered_set& privacySensitiveDisplays) REQUIRES(mLock); + /* This listener keeps tracks of visible privacy sensitive displays and updates the + * choreographer if there are any changes. + * + * Listener uses mListenerLock to guard all private data as choreographer and SurfaceComposer + * both can call into the listener. To prevent deadlocks Choreographer can call listener with + * its lock held, but listener must not call choreographer with its lock. + */ class PointerChoreographerDisplayInfoListener : public gui::WindowInfosListener { public: explicit PointerChoreographerDisplayInfoListener(PointerChoreographer* pc) : mPointerChoreographer(pc){}; void onWindowInfosChanged(const gui::WindowInfosUpdate&) override; + void setInitialDisplayInfos(const std::vector& windowInfos); + std::unordered_set getPrivacySensitiveDisplays(); void onPointerChoreographerDestroyed(); private: std::mutex mListenerLock; PointerChoreographer* mPointerChoreographer GUARDED_BY(mListenerLock); + std::unordered_set mPrivacySensitiveDisplays + GUARDED_BY(mListenerLock); }; sp mWindowInfoListener GUARDED_BY(mLock); diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h index cee44fc6b3..e34ed0fbd8 100644 --- a/services/inputflinger/include/PointerControllerInterface.h +++ b/services/inputflinger/include/PointerControllerInterface.h @@ -142,10 +142,13 @@ public: /* Sets the custom pointer icon for mice or styluses. */ virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0; - /* Sets the flag to skip screenshot of the pointer indicators on the display matching the - * provided displayId. + /* Sets the flag to skip screenshot of the pointer indicators on the display for the specified + * displayId. This flag can only be reset with resetSkipScreenshotFlags() */ - virtual void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) = 0; + virtual void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) = 0; + + /* Resets the flag to skip screenshot of the pointer indicators for all displays. */ + virtual void clearSkipScreenshotFlags() = 0; }; } // namespace android diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 2bb57b3e81..456013e347 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -76,13 +76,13 @@ void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCustomIconStyle = icon.style; } -void FakePointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { - if (skip) { - mDisplaysToSkipScreenshot.insert(displayId); - } else { - mDisplaysToSkipScreenshot.erase(displayId); - } -}; +void FakePointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) { + mDisplaysToSkipScreenshot.insert(displayId); +} + +void FakePointerController::clearSkipScreenshotFlags() { + mDisplaysToSkipScreenshot.clear(); +} void FakePointerController::assertViewportSet(ui::LogicalDisplayId displayId) { ASSERT_TRUE(mDisplayId); diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index 5bc713f80d..8d95f65896 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -45,7 +45,8 @@ public: void setDisplayViewport(const DisplayViewport& viewport) override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; + void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override; + void clearSkipScreenshotFlags() override; void fade(Transition) override; void assertViewportSet(ui::LogicalDisplayId displayId); diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 1689b33fe5..69a7382cd4 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -1636,72 +1636,69 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) { firstMousePc->assertPointerIconNotSet(); } -TEST_F_WITH_FLAGS(PointerChoreographerTest, HidesTouchSpotsOnMirroredDisplaysForSecureWindow, - REQUIRES_FLAGS_ENABLED( - ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) { - // Add a touch device and enable show touches. +using HidePointerForPrivacySensitiveDisplaysFixtureParam = + std::tuple, int32_t /*action*/>; + +class HidePointerForPrivacySensitiveDisplaysTestFixture + : public PointerChoreographerTest, + public ::testing::WithParamInterface {}; + +INSTANTIATE_TEST_SUITE_P( + PointerChoreographerTest, HidePointerForPrivacySensitiveDisplaysTestFixture, + ::testing::Values( + std::make_tuple( + "TouchSpots", AINPUT_SOURCE_TOUCHSCREEN, ControllerType::TOUCH, + FIRST_TOUCH_POINTER, + [](PointerChoreographer& pc) { pc.setShowTouchesEnabled(true); }, + AMOTION_EVENT_ACTION_DOWN), + std::make_tuple( + "Mouse", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE, MOUSE_POINTER, + [](PointerChoreographer& pc) {}, AMOTION_EVENT_ACTION_DOWN), + std::make_tuple( + "Stylus", AINPUT_SOURCE_STYLUS, ControllerType::STYLUS, STYLUS_POINTER, + [](PointerChoreographer& pc) { pc.setStylusPointerIconEnabled(true); }, + AMOTION_EVENT_ACTION_HOVER_ENTER), + std::make_tuple( + "DrawingTablet", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS, + ControllerType::MOUSE, STYLUS_POINTER, [](PointerChoreographer& pc) {}, + AMOTION_EVENT_ACTION_HOVER_ENTER)), + [](const testing::TestParamInfo& p) { + return std::string{std::get<0>(p.param)}; + }); + +TEST_P(HidePointerForPrivacySensitiveDisplaysTestFixture, + HidesPointerOnMirroredDisplaysForPrivacySensitiveDisplay) { + const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] = + GetParam(); + input_flags::hide_pointer_indicators_for_secure_windows(true); + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Add appropriate pointer device mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); - mChoreographer.setShowTouchesEnabled(true); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); + onControllerInit(mChoreographer); - // Emit touch events to create PointerController - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .pointer(FIRST_TOUCH_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); + // Emit input events to create PointerController + mChoreographer.notifyMotion(MotionArgsBuilder(action, source) + .pointer(pointerBuilder) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); - // By default touch indicators should not be hidden - auto pc = assertPointerControllerCreated(ControllerType::TOUCH); + // By default pointer indicators should not be hidden + auto pc = assertPointerControllerCreated(controllerType); pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); - // adding secure window on display should set flag to hide pointer indicators on corresponding - // mirrored display - gui::WindowInfo windowInfo; - windowInfo.displayId = DISPLAY_ID; - windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; - mChoreographer.onWindowInfosChanged({windowInfo}); + // marking a display privacy sensitive should set flag to hide pointer indicators on the + // corresponding mirrored display + mChoreographer.onPrivacySensitiveDisplaysChanged(/*privacySensitiveDisplays=*/{DISPLAY_ID}); pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/true); pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); - // removing the secure window should reset the state - windowInfo.inputConfig.clear(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY); - mChoreographer.onWindowInfosChanged({windowInfo}); - pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); - pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); -} - -TEST_F_WITH_FLAGS(PointerChoreographerTest, - DoesNotHidesTouchSpotsOnMirroredDisplaysForInvisibleWindow, - REQUIRES_FLAGS_ENABLED( - ACONFIG_FLAG(input_flags, hide_pointer_indicators_for_secure_windows))) { - // Add a touch device and enable show touches. - mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); - mChoreographer.setShowTouchesEnabled(true); - - // Emit touch events to create PointerController - mChoreographer.notifyMotion( - MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) - .pointer(FIRST_TOUCH_POINTER) - .deviceId(DEVICE_ID) - .displayId(DISPLAY_ID) - .build()); - - // By default touch indicators should not be hidden - auto pc = assertPointerControllerCreated(ControllerType::TOUCH); - pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); - pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); - - // adding secure but hidden window on display should still not set flag to hide pointer - // indicators - gui::WindowInfo windowInfo; - windowInfo.displayId = DISPLAY_ID; - windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; - windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE; - mChoreographer.onWindowInfosChanged({windowInfo}); + // un-marking the privacy sensitive display should reset the state + mChoreographer.onPrivacySensitiveDisplaysChanged(/*privacySensitiveDisplays=*/{}); pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); } -- GitLab From a3ba7fa4665a1df144ba777a37dbb979bab0d390 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 22 May 2024 16:34:52 -0400 Subject: [PATCH 388/465] Output::presentFrameAndReleaseLayers: flush pending commands for OFF displays Most work from this method can be skipped if the display is not enabled. However, uncaching buffers should still occur. If the display is disabled and there are buffers to uncache, still flush pending commands. They should only contain commands that are meaningful for a disabled display, like bufferSlotsToClear. Bug: 330806421 Test: libcompositionengine_test Flag: flush_buffer_slots_to_uncache Change-Id: I7baa3e76af86329fb266395e63e92a0ba38967f4 --- .../include/compositionengine/Output.h | 3 +- .../include/compositionengine/impl/Display.h | 1 + .../include/compositionengine/impl/Output.h | 6 +- .../include/compositionengine/mock/Output.h | 3 +- .../CompositionEngine/src/Display.cpp | 9 + .../CompositionEngine/src/Output.cpp | 19 +- .../CompositionEngine/tests/DisplayTest.cpp | 4 +- .../CompositionEngine/tests/MockHWComposer.h | 1 + .../CompositionEngine/tests/OutputTest.cpp | 175 +++++++++++++++--- .../DisplayHardware/HWComposer.cpp | 7 + .../DisplayHardware/HWComposer.h | 4 + .../surfaceflinger/common/FlagManager.cpp | 2 + .../common/include/common/FlagManager.h | 1 + .../surfaceflinger_flags_new.aconfig | 11 ++ 14 files changed, 209 insertions(+), 37 deletions(-) diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h index d420838807..191d475e5d 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h @@ -306,7 +306,7 @@ protected: virtual void finishFrame(GpuCompositionResult&&) = 0; virtual std::optional composeSurfaces( const Region&, std::shared_ptr, base::unique_fd&) = 0; - virtual void presentFrameAndReleaseLayers() = 0; + virtual void presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) = 0; virtual void renderCachedSets(const CompositionRefreshArgs&) = 0; virtual bool chooseCompositionStrategy( std::optional*) = 0; @@ -314,6 +314,7 @@ protected: const std::optional& changes) = 0; virtual bool getSkipColorTransform() const = 0; virtual FrameFences presentFrame() = 0; + virtual void executeCommands() = 0; virtual std::vector generateClientCompositionRequests( bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector &outLayerRef) = 0; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h index d87968f73c..d1eff241d3 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h @@ -60,6 +60,7 @@ public: void applyCompositionStrategy(const std::optional&) override; bool getSkipColorTransform() const override; compositionengine::Output::FrameFences presentFrame() override; + void executeCommands() override; void setExpensiveRenderingExpected(bool) override; void finishFrame(GpuCompositionResult&&) override; bool supportsOffloadPresent() const override; diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h index adcbbb943e..9990a742db 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h @@ -104,7 +104,7 @@ public: std::optional composeSurfaces(const Region&, std::shared_ptr, base::unique_fd&) override; - void presentFrameAndReleaseLayers() override; + void presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) override; void renderCachedSets(const CompositionRefreshArgs&) override; void cacheClientCompositionRequests(uint32_t) override; bool canPredictCompositionStrategy(const CompositionRefreshArgs&) override; @@ -123,7 +123,8 @@ public: virtual std::future chooseCompositionStrategyAsync( std::optional*); virtual void resetCompositionStrategy(); - virtual ftl::Future presentFrameAndReleaseLayersAsync(); + virtual ftl::Future presentFrameAndReleaseLayersAsync( + bool flushEvenWhenDisabled); protected: std::unique_ptr createOutputLayer(const sp&) const; @@ -137,6 +138,7 @@ protected: void applyCompositionStrategy(const std::optional&) override{}; bool getSkipColorTransform() const override; compositionengine::Output::FrameFences presentFrame() override; + void executeCommands() override {} virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings( const std::shared_ptr& buffer) const; std::vector generateClientCompositionRequests( diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h index 3f3deae972..d5bf2b5090 100644 --- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h +++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h @@ -121,9 +121,10 @@ public: base::unique_fd&)); MOCK_CONST_METHOD0(getSkipColorTransform, bool()); - MOCK_METHOD0(presentFrameAndReleaseLayers, void()); + MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled)); MOCK_METHOD1(renderCachedSets, void(const CompositionRefreshArgs&)); MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences()); + MOCK_METHOD(void, executeCommands, ()); MOCK_METHOD3(generateClientCompositionRequests, std::vector(bool, ui::Dataspace, std::vector&)); diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp index c18be7a8e8..81a95baac0 100644 --- a/services/surfaceflinger/CompositionEngine/src/Display.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp @@ -361,6 +361,15 @@ void Display::applyClientTargetRequests(const ClientTargetProperty& clientTarget static_cast(clientTargetProperty.clientTargetProperty.pixelFormat)); } +void Display::executeCommands() { + const auto halDisplayIdOpt = HalDisplayId::tryCast(mId); + if (mIsDisconnected || !halDisplayIdOpt) { + return; + } + + getCompositionEngine().getHwComposer().executeCommands(*halDisplayIdOpt); +} + compositionengine::Output::FrameFences Display::presentFrame() { auto fences = impl::Output::presentFrame(); diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 84f3f25eca..5b9a10252e 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -479,8 +479,9 @@ ftl::Future Output::present( devOptRepaintFlash(refreshArgs); finishFrame(std::move(result)); ftl::Future future; + const bool flushEvenWhenDisabled = !refreshArgs.bufferIdsToUncache.empty(); if (mOffloadPresent) { - future = presentFrameAndReleaseLayersAsync(); + future = presentFrameAndReleaseLayersAsync(flushEvenWhenDisabled); // Only offload for this frame. The next frame will determine whether it // needs to be offloaded. Leave the HwcAsyncWorker in place. For one thing, @@ -488,7 +489,7 @@ ftl::Future Output::present( // we don't want to churn. mOffloadPresent = false; } else { - presentFrameAndReleaseLayers(); + presentFrameAndReleaseLayers(flushEvenWhenDisabled); future = ftl::yield({}); } renderCachedSets(refreshArgs); @@ -1100,9 +1101,9 @@ void Output::prepareFrame() { finishPrepareFrame(); } -ftl::Future Output::presentFrameAndReleaseLayersAsync() { +ftl::Future Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) { return ftl::Future(std::move(mHwComposerAsyncWorker->send([&]() { - presentFrameAndReleaseLayers(); + presentFrameAndReleaseLayers(flushEvenWhenDisabled); return true; }))) .then([](bool) { return std::monostate{}; }); @@ -1177,7 +1178,8 @@ void Output::devOptRepaintFlash(const compositionengine::CompositionRefreshArgs& } } - presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); std::this_thread::sleep_for(*refreshArgs.devOptFlashDirtyRegionsDelay); @@ -1567,11 +1569,16 @@ bool Output::isPowerHintSessionGpuReportingEnabled() { return false; } -void Output::presentFrameAndReleaseLayers() { +void Output::presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) { ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str()); ALOGV(__FUNCTION__); if (!getState().isEnabled) { + if (flushEvenWhenDisabled && FlagManager::getInstance().flush_buffer_slots_to_uncache()) { + // Some commands, like clearing buffer slots, should still be executed + // even if the display is not enabled. + executeCommands(); + } return; } diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp index a95a5c62fd..39163ea60f 100644 --- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp @@ -1067,8 +1067,8 @@ TEST_F(DisplayFunctionalTest, presentFrameAndReleaseLayersCriticalCallsAreOrdere EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _)); EXPECT_CALL(*mDisplaySurface, onFrameCommitted()); - - mDisplay->presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mDisplay->presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } } // namespace diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h index 46121174e1..629d9f23ff 100644 --- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h +++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h @@ -63,6 +63,7 @@ public: (override)); MOCK_METHOD2(presentAndGetReleaseFences, status_t(HalDisplayId, std::optional)); + MOCK_METHOD(status_t, executeCommands, (HalDisplayId)); MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode)); MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t)); MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&)); diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp index 0dc3c9f60f..c34168d025 100644 --- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp +++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp @@ -2014,7 +2014,7 @@ struct OutputPresentTest : public testing::Test { MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult()); MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&)); MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&)); - MOCK_METHOD0(presentFrameAndReleaseLayers, void()); + MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override)); MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&)); MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&)); MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine), @@ -2046,7 +2046,7 @@ TEST_F(OutputPresentTest, justInvokesChildFunctionsInSequence) { EXPECT_CALL(mOutput, prepareFrame()); EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args))); EXPECT_CALL(mOutput, finishFrame(_)); - EXPECT_CALL(mOutput, presentFrameAndReleaseLayers()); + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(false)); EXPECT_CALL(mOutput, renderCachedSets(Ref(args))); mOutput.present(args); @@ -2067,7 +2067,7 @@ TEST_F(OutputPresentTest, predictingCompositionStrategyInvokesPrepareFrameAsync) EXPECT_CALL(mOutput, prepareFrameAsync()); EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args))); EXPECT_CALL(mOutput, finishFrame(_)); - EXPECT_CALL(mOutput, presentFrameAndReleaseLayers()); + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(false)); EXPECT_CALL(mOutput, renderCachedSets(Ref(args))); mOutput.present(args); @@ -2913,7 +2913,7 @@ struct OutputDevOptRepaintFlashTest : public testing::Test { std::optional(const Region&, std::shared_ptr, base::unique_fd&)); - MOCK_METHOD0(presentFrameAndReleaseLayers, void()); + MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled)); MOCK_METHOD0(prepareFrame, void()); MOCK_METHOD0(updateProtectedContentState, void()); MOCK_METHOD2(dequeueRenderBuffer, @@ -2950,7 +2950,8 @@ TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfNotEnabled) { mOutput.mState.isEnabled = false; InSequence seq; - EXPECT_CALL(mOutput, presentFrameAndReleaseLayers()); + constexpr bool kFlushEvenWhenDisabled = false; + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)); EXPECT_CALL(mOutput, prepareFrame()); mOutput.devOptRepaintFlash(mRefreshArgs); @@ -2962,7 +2963,8 @@ TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfEnabled) { InSequence seq; EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kEmptyRegion)); - EXPECT_CALL(mOutput, presentFrameAndReleaseLayers()); + constexpr bool kFlushEvenWhenDisabled = false; + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)); EXPECT_CALL(mOutput, prepareFrame()); mOutput.devOptRepaintFlash(mRefreshArgs); @@ -2978,7 +2980,8 @@ TEST_F(OutputDevOptRepaintFlashTest, alsoComposesSurfacesAndQueuesABufferIfDirty EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)); EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _)); EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f)); - EXPECT_CALL(mOutput, presentFrameAndReleaseLayers()); + constexpr bool kFlushEvenWhenDisabled = false; + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)); EXPECT_CALL(mOutput, prepareFrame()); mOutput.devOptRepaintFlash(mRefreshArgs); @@ -2996,7 +2999,7 @@ struct OutputFinishFrameTest : public testing::Test { std::optional(const Region&, std::shared_ptr, base::unique_fd&)); - MOCK_METHOD0(presentFrameAndReleaseLayers, void()); + MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override)); MOCK_METHOD0(updateProtectedContentState, void()); MOCK_METHOD2(dequeueRenderBuffer, bool(base::unique_fd*, std::shared_ptr*)); @@ -3139,7 +3142,8 @@ struct OutputPostFramebufferTest : public testing::Test { struct OutputPartialMock : public OutputPartialMockBase { // Sets up the helper functions called by the function under test to use // mock implementations. - MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences()); + MOCK_METHOD(compositionengine::Output::FrameFences, presentFrame, ()); + MOCK_METHOD(void, executeCommands, ()); }; struct Layer { @@ -3177,9 +3181,67 @@ struct OutputPostFramebufferTest : public testing::Test { }; TEST_F(OutputPostFramebufferTest, ifNotEnabledDoesNothing) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache, + true); mOutput.mState.isEnabled = false; + EXPECT_CALL(mOutput, executeCommands()).Times(0); + EXPECT_CALL(mOutput, presentFrame()).Times(0); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); +} + +TEST_F(OutputPostFramebufferTest, ifNotEnabledExecutesCommandsIfFlush) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache, + true); + mOutput.mState.isEnabled = false; + EXPECT_CALL(mOutput, executeCommands()); + EXPECT_CALL(mOutput, presentFrame()).Times(0); + + constexpr bool kFlushEvenWhenDisabled = true; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); +} + +TEST_F(OutputPostFramebufferTest, ifEnabledDoNotExecuteCommands) { + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache, + true); + mOutput.mState.isEnabled = true; + + compositionengine::Output::FrameFences frameFences; + + EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u)); + + // This should only be called for disabled outputs. This test's goal is to verify this line; + // the other expectations help satisfy the StrictMocks. + EXPECT_CALL(mOutput, executeCommands()).Times(0); + + EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences)); + EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); + + constexpr bool kFlushEvenWhenDisabled = true; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); +} + +TEST_F(OutputPostFramebufferTest, ifEnabledDoNotExecuteCommands2) { + // Same test as ifEnabledDoNotExecuteCommands, but with this variable set to false. + constexpr bool kFlushEvenWhenDisabled = false; + + SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache, + true); + mOutput.mState.isEnabled = true; + + compositionengine::Output::FrameFences frameFences; + + EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u)); + + // This should only be called for disabled outputs. This test's goal is to verify this line; + // the other expectations help satisfy the StrictMocks. + EXPECT_CALL(mOutput, executeCommands()).Times(0); + + EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences)); + EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); + + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCompleted) { @@ -3197,7 +3259,8 @@ TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCom EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences)); EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted()); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = true; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { @@ -3241,7 +3304,8 @@ TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) { EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get()); }); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) { @@ -3282,7 +3346,8 @@ TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) { EXPECT_EQ(FenceResult(layer3Fence), releaseFence); }); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) { @@ -3308,7 +3373,8 @@ TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed).WillOnce(Return()); EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed).WillOnce(Return()); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) { @@ -3333,7 +3399,8 @@ TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFenc EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return()); EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return()); EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return()); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); } TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) { @@ -3381,7 +3448,8 @@ TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) { EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get()); }); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); // After the call the list of released layers should have been cleared. EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty()); @@ -3429,7 +3497,8 @@ TEST_F(OutputPostFramebufferTest, setReleasedLayersSentPresentFence) { EXPECT_EQ(FenceResult(presentFence), fenceResult); }); - mOutput.presentFrameAndReleaseLayers(); + constexpr bool kFlushEvenWhenDisabled = false; + mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled); // After the call the list of released layers should have been cleared. EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty()); @@ -5272,8 +5341,9 @@ struct OutputPresentFrameAndReleaseLayersAsyncTest : public ::testing::Test { struct OutputPartialMock : public OutputPrepareFrameAsyncTest::OutputPartialMock { // Set up the helper functions called by the function under test to use // mock implementations. - MOCK_METHOD0(presentFrameAndReleaseLayers, void()); - MOCK_METHOD0(presentFrameAndReleaseLayersAsync, ftl::Future()); + MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled)); + MOCK_METHOD(ftl::Future, presentFrameAndReleaseLayersAsync, + (bool flushEvenWhenDisabled)); }; OutputPresentFrameAndReleaseLayersAsyncTest() { mOutput->setDisplayColorProfileForTest( @@ -5290,16 +5360,16 @@ struct OutputPresentFrameAndReleaseLayersAsyncTest : public ::testing::Test { }; TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, notCalledWhenNotRequested) { - EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()).Times(0); - EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1); + EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(_)).Times(0); + EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(_)).Times(1); mOutput->present(mRefreshArgs); } TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledWhenRequested) { - EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()) + EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(false)) .WillOnce(Return(ftl::yield({}))); - EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(0); + EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(_)).Times(0); mOutput->offloadPresentNextFrame(); mOutput->present(mRefreshArgs); @@ -5307,9 +5377,10 @@ TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledWhenRequested) { TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledForOneFrame) { ::testing::InSequence inseq; - EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()) + constexpr bool kFlushEvenWhenDisabled = false; + EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(kFlushEvenWhenDisabled)) .WillOnce(Return(ftl::yield({}))); - EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1); + EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)).Times(1); mOutput->offloadPresentNextFrame(); mOutput->present(mRefreshArgs); @@ -5390,5 +5461,59 @@ TEST_F(OutputUpdateProtectedContentStateTest, ifProtectedContentLayerComposeByCl mOutput.updateProtectedContentState(); } +struct OutputPresentFrameAndReleaseLayersTest : public testing::Test { + struct OutputPartialMock : public OutputPartialMockBase { + // Sets up the helper functions called by the function under test (and functions we can + // ignore) to use mock implementations. + MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD1(updateCompositionState, + void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD0(planComposition, void()); + MOCK_METHOD1(writeCompositionState, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD0(beginFrame, void()); + MOCK_METHOD0(prepareFrame, void()); + MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult()); + MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&)); + MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override)); + MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&)); + MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&)); + MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine), + (override)); + MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override)); + MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override)); + }; + + OutputPresentFrameAndReleaseLayersTest() { + EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true)); + EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true)); + } + + NiceMock mOutput; +}; + +TEST_F(OutputPresentFrameAndReleaseLayersTest, noBuffersToUncache) { + CompositionRefreshArgs args; + ASSERT_TRUE(args.bufferIdsToUncache.empty()); + mOutput.editState().isEnabled = false; + + constexpr bool kFlushEvenWhenDisabled = false; + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)); + + mOutput.present(args); +} + +TEST_F(OutputPresentFrameAndReleaseLayersTest, buffersToUncache) { + CompositionRefreshArgs args; + args.bufferIdsToUncache.push_back(1); + mOutput.editState().isEnabled = false; + + constexpr bool kFlushEvenWhenDisabled = true; + EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)); + + mOutput.present(args); +} + } // namespace } // namespace android::compositionengine diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp index 3cfb9cac86..3d285a84ef 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -600,6 +600,13 @@ status_t HWComposer::presentAndGetReleaseFences( return NO_ERROR; } +status_t HWComposer::executeCommands(HalDisplayId displayId) { + auto& hwcDisplay = mDisplayData[displayId].hwcDisplay; + auto error = static_cast(mComposer->executeCommands(hwcDisplay->getId())); + RETURN_IF_HWC_ERROR_FOR("executeCommands", error, displayId, UNKNOWN_ERROR); + return NO_ERROR; +} + status_t HWComposer::setPowerMode(PhysicalDisplayId displayId, hal::PowerMode mode) { RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX); diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h index bc32cda059..9368b7b6dd 100644 --- a/services/surfaceflinger/DisplayHardware/HWComposer.h +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -160,6 +160,8 @@ public: HalDisplayId, std::optional earliestPresentTime) = 0; + virtual status_t executeCommands(HalDisplayId) = 0; + // set power mode virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0; @@ -361,6 +363,8 @@ public: HalDisplayId, std::optional earliestPresentTime) override; + status_t executeCommands(HalDisplayId) override; + // set power mode status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override; diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index 57b170fba3..b78809a59a 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -148,6 +148,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(commit_not_composited); DUMP_READ_ONLY_FLAG(local_tonemap_screenshots); DUMP_READ_ONLY_FLAG(override_trusted_overlay); + DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -246,6 +247,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, ""); FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, ""); FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots"); FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, ""); +FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 9517ff7efb..302d311bb9 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -87,6 +87,7 @@ public: bool commit_not_composited() const; bool local_tonemap_screenshots() const; bool override_trusted_overlay() const; + bool flush_buffer_slots_to_uncache() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 02d8819785..07728912f1 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -54,6 +54,17 @@ flag { } } # detached_mirror +flag { + name: "flush_buffer_slots_to_uncache" + namespace: "core_graphics" + description: "Flush DisplayCommands for disabled displays in order to uncache requested buffers." + bug: "330806421" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # flush_buffer_slots_to_uncache + flag { name: "frame_rate_category_mrr" namespace: "core_graphics" -- GitLab From f50323a1538fc8da33a14c8ab2ee2fb02d45123d Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 29 May 2024 08:52:08 -0700 Subject: [PATCH 389/465] Ensure objects remain valid after calling policy The function afterKeyEventLockedInterruptable releases the lock and calls into policy. During this time, the call to "removeInputChannel" might come in. This call would cause the waitQueue to be drained. Therefore, the dispatchEntry that's stored in this queue would be deleted. Before this CL, we obtained a reference to the EventEntry object before calling policy. If there aren't any more strong pointers remaining to the EventEntry, the object would become deleted, and the reference would end up pointing to freed memory. Previous flow of events: - KeyEntry is allocated during setFocusedWindow call, as part of "synthesizeCancelationEvents". - App calls "finish" on an event, and dispatcher notifies policy about the unhandled key event. But dispatcher must release lock before calling policy. - After dispatcher has released the lock, but before it called policy, there is a binder call to "removeInputChannel" that comes in. That causes the waitQueue to be drained, and deletes the DispatchEntry. If the dispatch entry is the last remaining reference to the KeyEntry, then the KeyEntry gets deleted, as well. - The dispatcher calls policy, and uses the reference to the KeyEntry that it was provided. But that reference points to freed memory. This causes a crash. To deal with this, make a few changes in this CL: - Since the "doDispatchCycleFinishedCommand" is stored in queue, it should have a strong pointer to the connection object, and not just a reference. That means the Connection object will be valid when the command actually runs (otherwise, someone might delete it) - Inside afterKeyEventLockedInterruptable, assume that the dispatchEntry will be deleted after the lock is released. Make copies of the data that we need after the lock is regained: 1) Add refcount for EventEntry 2) Store the "hasForegroundTarget" into a separate variable (technically, it's not necessary, but it allows us to remove all usages of "dispatchEntry" in the rest of the function. As an alternative, we could re-look up the DispatchEntry in the waitQueue after we regain the lock, but that seems more complex in terms of implementation / readability. Bug: 343129193 Test: atest --host inputflinger_tests Change-Id: Ibea7117e4c85cd1e98bbd01872ce249cbb2d54bd --- .../dispatcher/InputDispatcher.cpp | 18 +++++++++++++----- .../inputflinger/dispatcher/InputDispatcher.h | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index dae2b6119c..9b97629b2e 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -6388,9 +6388,8 @@ void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime, } if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) { - const KeyEntry& keyEntry = static_cast(*(dispatchEntry.eventEntry)); fallbackKeyEntry = - afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled); + afterKeyEventLockedInterruptable(connection, &dispatchEntry, handled); } } // End critical section: The -LockedInterruptable methods may have released the lock. @@ -6614,8 +6613,17 @@ void InputDispatcher::processConnectionResponsiveLocked(const Connection& connec } std::unique_ptr InputDispatcher::afterKeyEventLockedInterruptable( - const std::shared_ptr& connection, DispatchEntry& dispatchEntry, - const KeyEntry& keyEntry, bool handled) { + const std::shared_ptr& connection, DispatchEntry* dispatchEntry, bool handled) { + // The dispatchEntry is currently valid, but it might point to a deleted object after we release + // the lock. For simplicity, make copies of the data of interest here and assume that + // 'dispatchEntry' is not valid after this section. + // Hold a strong reference to the EventEntry to ensure it's valid for the duration of this + // function, even if the DispatchEntry gets destroyed and releases its share of the ownership. + std::shared_ptr eventEntry = dispatchEntry->eventEntry; + const bool hasForegroundTarget = dispatchEntry->hasForegroundTarget(); + const KeyEntry& keyEntry = static_cast(*(eventEntry)); + // To prevent misuse, ensure dispatchEntry is no longer valid. + dispatchEntry = nullptr; if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) { if (!handled) { // Report the key as unhandled, since the fallback was not handled. @@ -6632,7 +6640,7 @@ std::unique_ptr InputDispatcher::afterKeyEventLockedInterruptabl connection->inputState.removeFallbackKey(originalKeyCode); } - if (handled || !dispatchEntry.hasForegroundTarget()) { + if (handled || !hasForegroundTarget) { // If the application handles the original key for which we previously // generated a fallback or if the window is not a foreground window, // then cancel the associated fallback key, if any. diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h index 6240e7fe72..e2fc7a0d4f 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.h +++ b/services/inputflinger/dispatcher/InputDispatcher.h @@ -687,8 +687,8 @@ private: std::map mVerifiersByDisplay; // Returns a fallback KeyEntry that should be sent to the connection, if required. std::unique_ptr afterKeyEventLockedInterruptable( - const std::shared_ptr& connection, DispatchEntry& dispatchEntry, - const KeyEntry& keyEntry, bool handled) REQUIRES(mLock); + const std::shared_ptr& connection, DispatchEntry* dispatchEntry, + bool handled) REQUIRES(mLock); // Find touched state and touched window by token. std::tuple -- GitLab From 9c933d056b60a1a6eeb71a13f4fb60c64405464a Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Wed, 29 May 2024 10:21:19 -0700 Subject: [PATCH 390/465] Override maxValue when the initial value is negative or zero The fuzzer can provide negative values for maxValue. Generally, we would fix the fuzzer to provide values in a different range. However, this exact scenario is already handled (partially) in the constructor. So instead, for simplicity, override this value to something valid (like 1). Bug: 342826711 Test: FUZZER=inputflinger_touchpad_input_fuzzer; m $FUZZER && ASAN_OPTIONS=detect_odr_violation=0 $ANDROID_HOST_OUT/fuzz/x86_64/$FUZZER/$FUZZER ~/clusterfuzz-testcase-minimized-inputflinger_touchpad_input_fuzzer-5915386382188544 Change-Id: Iee9deec102b577484f66df759a037af72da6a1cd --- .../inputflinger/reader/mapper/TouchpadInputMapper.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index b8911db2da..daf99dae87 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -241,10 +242,10 @@ TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext, mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) { RawAbsoluteAxisInfo slotAxisInfo; deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo); - if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) { - ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work " - "properly.", - deviceContext.getName().c_str()); + if (!slotAxisInfo.valid || slotAxisInfo.maxValue < 0) { + LOG(WARNING) << "Touchpad " << deviceContext.getName() + << " doesn't have a valid ABS_MT_SLOT axis, and probably won't work properly."; + slotAxisInfo.maxValue = 0; } mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true); -- GitLab From bd49b28c7fccb17c4c402a66ff79dfab696fb9e6 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 23 May 2024 18:02:54 +0000 Subject: [PATCH 391/465] Add Tests for PointerChoreographerDisplayInfoListener This CL allowes DisplayInfoListener to be injected into PointerChoreographer. This enables testing the logic inside the listener. Test: atest inputflinger_tests Bug: 325252005 Change-Id: I0101119313070f9b0a4755398ea81ce1d0681faf --- .../inputflinger/PointerChoreographer.cpp | 48 ++- services/inputflinger/PointerChoreographer.h | 20 +- .../tests/FakePointerController.cpp | 24 +- .../tests/FakePointerController.h | 6 +- services/inputflinger/tests/InterfaceMocks.h | 10 + .../tests/PointerChoreographer_test.cpp | 314 ++++++++++++++++-- 6 files changed, 366 insertions(+), 56 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 02453ef7a0..7d3a2df550 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -30,10 +30,6 @@ namespace android { -namespace input_flags = com::android::input::flags; -static const bool HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS = - input_flags::hide_pointer_indicators_for_secure_windows(); - namespace { bool isFromMouse(const NotifyMotionArgs& args) { @@ -106,8 +102,31 @@ std::unordered_set getPrivacySensitiveDisplaysFromWindowIn // --- PointerChoreographer --- -PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, +PointerChoreographer::PointerChoreographer(InputListenerInterface& inputListener, PointerChoreographerPolicyInterface& policy) + : PointerChoreographer( + inputListener, policy, + [](const sp& listener) { + auto initialInfo = std::make_pair(std::vector{}, + std::vector{}); +#if defined(__ANDROID__) + SurfaceComposerClient::getDefault()->addWindowInfosListener(listener, + &initialInfo); +#endif + return initialInfo.first; + }, + [](const sp& listener) { +#if defined(__ANDROID__) + SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener); +#endif + }) { +} + +PointerChoreographer::PointerChoreographer( + android::InputListenerInterface& listener, + android::PointerChoreographerPolicyInterface& policy, + const android::PointerChoreographer::WindowListenerRegisterConsumer& registerListener, + const android::PointerChoreographer::WindowListenerUnregisterConsumer& unregisterListener) : mTouchControllerConstructor([this]() { return mPolicy.createPointerController( PointerControllerInterface::ControllerType::TOUCH); @@ -117,7 +136,9 @@ PointerChoreographer::PointerChoreographer(InputListenerInterface& listener, mDefaultMouseDisplayId(ui::LogicalDisplayId::DEFAULT), mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID), mShowTouchesEnabled(false), - mStylusPointerIconEnabled(false) {} + mStylusPointerIconEnabled(false), + mRegisterListener(registerListener), + mUnregisterListener(unregisterListener) {} PointerChoreographer::~PointerChoreographer() { std::scoped_lock _l(mLock); @@ -125,6 +146,7 @@ PointerChoreographer::~PointerChoreographer() { return; } mWindowInfoListener->onPointerChoreographerDestroyed(); + mUnregisterListener(mWindowInfoListener); } void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) { @@ -391,7 +413,7 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) } void PointerChoreographer::onControllerAddedOrRemovedLocked() { - if (!HIDE_TOUCH_INDICATORS_FOR_SECURE_WINDOWS) { + if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) { return; } bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() || @@ -399,18 +421,10 @@ void PointerChoreographer::onControllerAddedOrRemovedLocked() { if (requireListener && mWindowInfoListener == nullptr) { mWindowInfoListener = sp::make(this); - auto initialInfo = std::make_pair(std::vector{}, - std::vector{}); -#if defined(__ANDROID__) - SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener, - &initialInfo); -#endif - mWindowInfoListener->setInitialDisplayInfos(initialInfo.first); + mWindowInfoListener->setInitialDisplayInfos(mRegisterListener(mWindowInfoListener)); onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays()); } else if (!requireListener && mWindowInfoListener != nullptr) { -#if defined(__ANDROID__) - SurfaceComposerClient::getDefault()->removeWindowInfosListener(mWindowInfoListener); -#endif + mUnregisterListener(mWindowInfoListener); mWindowInfoListener = nullptr; } else if (requireListener && mWindowInfoListener != nullptr) { // controller may have been added to an existing privacy sensitive display, we need to diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index 11c5a0c6c7..d9b075f3ee 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -108,10 +108,6 @@ public: void notifyDeviceReset(const NotifyDeviceResetArgs& args) override; void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override; - // Public because it's also used by tests to simulate the WindowInfosListener callback - void onPrivacySensitiveDisplaysChanged( - const std::unordered_set& privacySensitiveDisplays); - void dump(std::string& dump) override; private: @@ -139,6 +135,8 @@ private: void onPrivacySensitiveDisplaysChangedLocked( const std::unordered_set& privacySensitiveDisplays) REQUIRES(mLock); + void onPrivacySensitiveDisplaysChanged( + const std::unordered_set& privacySensitiveDisplays); /* This listener keeps tracks of visible privacy sensitive displays and updates the * choreographer if there are any changes. @@ -194,6 +192,20 @@ private: bool mShowTouchesEnabled GUARDED_BY(mLock); bool mStylusPointerIconEnabled GUARDED_BY(mLock); std::set mDisplaysWithPointersHidden; + +protected: + using WindowListenerRegisterConsumer = std::function( + const sp&)>; + using WindowListenerUnregisterConsumer = + std::function&)>; + explicit PointerChoreographer(InputListenerInterface& listener, + PointerChoreographerPolicyInterface&, + const WindowListenerRegisterConsumer& registerListener, + const WindowListenerUnregisterConsumer& unregisterListener); + +private: + const WindowListenerRegisterConsumer mRegisterListener; + const WindowListenerUnregisterConsumer mUnregisterListener; }; } // namespace android diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp index 456013e347..d0998ba851 100644 --- a/services/inputflinger/tests/FakePointerController.cpp +++ b/services/inputflinger/tests/FakePointerController.cpp @@ -77,10 +77,12 @@ void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) { } void FakePointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) { + mDisplaysToSkipScreenshotFlagChanged = true; mDisplaysToSkipScreenshot.insert(displayId); } void FakePointerController::clearSkipScreenshotFlags() { + mDisplaysToSkipScreenshotFlagChanged = true; mDisplaysToSkipScreenshot.clear(); } @@ -125,13 +127,21 @@ void FakePointerController::assertCustomPointerIconNotSet() { ASSERT_EQ(std::nullopt, mCustomIconStyle); } -void FakePointerController::assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId, - bool isHidden) { - if (isHidden) { - ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end()); - } else { - ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end()); - } +void FakePointerController::assertIsSkipScreenshotFlagSet(ui::LogicalDisplayId displayId) { + ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end()); +} + +void FakePointerController::assertIsSkipScreenshotFlagNotSet(ui::LogicalDisplayId displayId) { + ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end()); +} + +void FakePointerController::assertSkipScreenshotFlagChanged() { + ASSERT_TRUE(mDisplaysToSkipScreenshotFlagChanged); + mDisplaysToSkipScreenshotFlagChanged = false; +} + +void FakePointerController::assertSkipScreenshotFlagNotChanged() { + ASSERT_FALSE(mDisplaysToSkipScreenshotFlagChanged); } bool FakePointerController::isPointerShown() { diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h index 8d95f65896..2c76c6214c 100644 --- a/services/inputflinger/tests/FakePointerController.h +++ b/services/inputflinger/tests/FakePointerController.h @@ -57,7 +57,10 @@ public: void assertPointerIconNotSet(); void assertCustomPointerIconSet(PointerIconStyle iconId); void assertCustomPointerIconNotSet(); - void assertIsHiddenOnMirroredDisplays(ui::LogicalDisplayId displayId, bool isHidden); + void assertIsSkipScreenshotFlagSet(ui::LogicalDisplayId displayId); + void assertIsSkipScreenshotFlagNotSet(ui::LogicalDisplayId displayId); + void assertSkipScreenshotFlagChanged(); + void assertSkipScreenshotFlagNotChanged(); bool isPointerShown(); private: @@ -81,6 +84,7 @@ private: std::map> mSpotsByDisplay; std::unordered_set mDisplaysToSkipScreenshot; + bool mDisplaysToSkipScreenshotFlagChanged{false}; }; } // namespace android diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index 6389cdc5fb..6a35631e46 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -27,7 +27,9 @@ #include #include +#include #include +#include #include #include #include @@ -174,4 +176,12 @@ public: MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override)); }; +class MockPointerChoreographerPolicyInterface : public PointerChoreographerPolicyInterface { +public: + MOCK_METHOD(std::shared_ptr, createPointerController, + (PointerControllerInterface::ControllerType), (override)); + MOCK_METHOD(void, notifyPointerDisplayIdChanged, + (ui::LogicalDisplayId displayId, const FloatPoint& position), (override)); +}; + } // namespace android diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 69a7382cd4..b517928f66 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -22,6 +22,7 @@ #include #include "FakePointerController.h" +#include "InterfaceMocks.h" #include "NotifyArgsBuilders.h" #include "TestEventMatchers.h" #include "TestInputListener.h" @@ -89,10 +90,41 @@ static std::vector createViewports(std::vector& windowInfoListener, + const std::vector& mInitialWindowInfos); +}; + +TestPointerChoreographer::TestPointerChoreographer( + InputListenerInterface& inputListener, PointerChoreographerPolicyInterface& policy, + sp& windowInfoListener, + const std::vector& mInitialWindowInfos) + : PointerChoreographer( + inputListener, policy, + [&windowInfoListener, + &mInitialWindowInfos](const sp& listener) { + windowInfoListener = listener; + return mInitialWindowInfos; + }, + [&windowInfoListener](const sp& listener) { + windowInfoListener = nullptr; + }) {} + class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface { protected: TestInputListener mTestListener; - PointerChoreographer mChoreographer{mTestListener, *this}; + sp mRegisteredWindowInfoListener; + std::vector mInjectedInitialWindowInfos; + TestPointerChoreographer mChoreographer{mTestListener, *this, mRegisteredWindowInfoListener, + mInjectedInitialWindowInfos}; + + void SetUp() override { + // flag overrides + input_flags::hide_pointer_indicators_for_secure_windows(true); + } std::shared_ptr assertPointerControllerCreated( ControllerType expectedType) { @@ -131,6 +163,16 @@ protected: void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); } + void assertWindowInfosListenerRegistered() { + ASSERT_NE(nullptr, mRegisteredWindowInfoListener) + << "WindowInfosListener was not registered"; + } + + void assertWindowInfosListenerNotRegistered() { + ASSERT_EQ(nullptr, mRegisteredWindowInfoListener) + << "WindowInfosListener was not unregistered"; + } + private: std::deque>> mCreatedControllers; @@ -1636,16 +1678,36 @@ TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) { firstMousePc->assertPointerIconNotSet(); } -using HidePointerForPrivacySensitiveDisplaysFixtureParam = +using SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam = std::tuple, int32_t /*action*/>; -class HidePointerForPrivacySensitiveDisplaysTestFixture +class SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture : public PointerChoreographerTest, - public ::testing::WithParamInterface {}; + public ::testing::WithParamInterface< + SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam> { +protected: + void initializePointerDevice(const PointerBuilder& pointerBuilder, const uint32_t source, + const std::function onControllerInit, + const int32_t action) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Add appropriate pointer device + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); + onControllerInit(mChoreographer); + + // Emit input events to create PointerController + mChoreographer.notifyMotion(MotionArgsBuilder(action, source) + .pointer(pointerBuilder) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + } +}; INSTANTIATE_TEST_SUITE_P( - PointerChoreographerTest, HidePointerForPrivacySensitiveDisplaysTestFixture, + PointerChoreographerTest, SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture, ::testing::Values( std::make_tuple( "TouchSpots", AINPUT_SOURCE_TOUCHSCREEN, ControllerType::TOUCH, @@ -1663,44 +1725,205 @@ INSTANTIATE_TEST_SUITE_P( "DrawingTablet", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS, ControllerType::MOUSE, STYLUS_POINTER, [](PointerChoreographer& pc) {}, AMOTION_EVENT_ACTION_HOVER_ENTER)), - [](const testing::TestParamInfo& p) { + [](const testing::TestParamInfo< + SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam>& p) { return std::string{std::get<0>(p.param)}; }); -TEST_P(HidePointerForPrivacySensitiveDisplaysTestFixture, - HidesPointerOnMirroredDisplaysForPrivacySensitiveDisplay) { +TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture, + WindowInfosListenerIsOnlyRegisteredWhenRequired) { + const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] = + GetParam(); + assertWindowInfosListenerNotRegistered(); + + // Listener should registered when a pointer device is added + initializePointerDevice(pointerBuilder, source, onControllerInit, action); + assertWindowInfosListenerRegistered(); + + mChoreographer.notifyInputDevicesChanged({}); + assertWindowInfosListenerNotRegistered(); +} + +TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture, + InitialDisplayInfoIsPopulatedForListener) { + const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] = + GetParam(); + // listener should not be registered if there is no pointer device + assertWindowInfosListenerNotRegistered(); + + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + mInjectedInitialWindowInfos = {windowInfo}; + + initializePointerDevice(pointerBuilder, source, onControllerInit, action); + assertWindowInfosListenerRegistered(); + + // Pointer indicators should be hidden based on the initial display info + auto pc = assertPointerControllerCreated(controllerType); + pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); + + // un-marking the privacy sensitive display should reset the state + windowInfo.inputConfig.clear(); + gui::DisplayInfo displayInfo; + displayInfo.displayId = DISPLAY_ID; + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); + + pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); +} + +TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture, + SkipsPointerScreenshotForPrivacySensitiveWindows) { + const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] = + GetParam(); + initializePointerDevice(pointerBuilder, source, onControllerInit, action); + + // By default pointer indicators should not be hidden + auto pc = assertPointerControllerCreated(controllerType); + pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); + + // marking a display privacy sensitive should set flag to hide pointer indicators on the + // display screenshot + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + gui::DisplayInfo displayInfo; + displayInfo.displayId = DISPLAY_ID; + assertWindowInfosListenerRegistered(); + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); + + pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); + + // un-marking the privacy sensitive display should reset the state + windowInfo.inputConfig.clear(); + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); + + pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); +} + +TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture, + DoesNotSkipPointerScreenshotForHiddenPrivacySensitiveWindows) { const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] = GetParam(); - input_flags::hide_pointer_indicators_for_secure_windows(true); + initializePointerDevice(pointerBuilder, source, onControllerInit, action); + + // By default pointer indicators should not be hidden + auto pc = assertPointerControllerCreated(controllerType); + pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); + + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE; + gui::DisplayInfo displayInfo; + displayInfo.displayId = DISPLAY_ID; + assertWindowInfosListenerRegistered(); + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); + + pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); +} + +TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture, + DoesNotUpdateControllerForUnchangedPrivacySensitiveWindows) { + const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] = + GetParam(); + initializePointerDevice(pointerBuilder, source, onControllerInit, action); + + auto pc = assertPointerControllerCreated(controllerType); + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + gui::DisplayInfo displayInfo; + displayInfo.displayId = DISPLAY_ID; + assertWindowInfosListenerRegistered(); + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); + + gui::WindowInfo windowInfo2 = windowInfo; + windowInfo2.inputConfig.clear(); + pc->assertSkipScreenshotFlagChanged(); + + // controller should not be updated if there are no changes in privacy sensitive windows + mRegisteredWindowInfoListener->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo, windowInfo2}, + {displayInfo}, + /*vsyncId=*/0, + /*timestamp=*/0}); + pc->assertSkipScreenshotFlagNotChanged(); +} + +TEST_F_WITH_FLAGS( + PointerChoreographerTest, HidesPointerScreenshotForExistingPrivacySensitiveWindows, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + hide_pointer_indicators_for_secure_windows))) { mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); - // Add appropriate pointer device + // Add a first mouse device mChoreographer.notifyInputDevicesChanged( - {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}}); - onControllerInit(mChoreographer); + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); - // Emit input events to create PointerController - mChoreographer.notifyMotion(MotionArgsBuilder(action, source) - .pointer(pointerBuilder) + mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) .deviceId(DEVICE_ID) .displayId(DISPLAY_ID) .build()); - // By default pointer indicators should not be hidden - auto pc = assertPointerControllerCreated(controllerType); - pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); - pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + gui::DisplayInfo displayInfo; + displayInfo.displayId = DISPLAY_ID; + assertWindowInfosListenerRegistered(); + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); - // marking a display privacy sensitive should set flag to hide pointer indicators on the - // corresponding mirrored display - mChoreographer.onPrivacySensitiveDisplaysChanged(/*privacySensitiveDisplays=*/{DISPLAY_ID}); - pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/true); - pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); + + // Add a second touch device and controller + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}}); + mChoreographer.setShowTouchesEnabled(true); + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(FIRST_TOUCH_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + + // Pointer indicators should be hidden for this controller by default + auto pc2 = assertPointerControllerCreated(ControllerType::TOUCH); + pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); // un-marking the privacy sensitive display should reset the state - mChoreographer.onPrivacySensitiveDisplaysChanged(/*privacySensitiveDisplays=*/{}); - pc->assertIsHiddenOnMirroredDisplays(DISPLAY_ID, /*isHidden=*/false); - pc->assertIsHiddenOnMirroredDisplays(ANOTHER_DISPLAY_ID, /*isHidden=*/false); + windowInfo.inputConfig.clear(); + mRegisteredWindowInfoListener + ->onWindowInfosChanged(/*windowInfosUpdate=*/ + {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); + + pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); + pc2->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID); + pc2->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID); } TEST_P(StylusTestFixture, SetsPointerIconForStylus) { @@ -2070,4 +2293,41 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { assertPointerControllerRemoved(pc); } +class PointerChoreographerWindowInfoListenerTest : public testing::Test {}; + +TEST_F_WITH_FLAGS( + PointerChoreographerWindowInfoListenerTest, + doesNotCrashIfListenerCalledAfterPointerChoreographerDestroyed, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags, + hide_pointer_indicators_for_secure_windows))) { + sp registeredListener; + sp localListenerCopy; + { + testing::NiceMock mockPolicy; + EXPECT_CALL(mockPolicy, createPointerController(ControllerType::MOUSE)) + .WillOnce(testing::Return(std::make_shared())); + TestInputListener testListener; + std::vector injectedInitialWindowInfos; + TestPointerChoreographer testChoreographer{testListener, mockPolicy, registeredListener, + injectedInitialWindowInfos}; + testChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Add mouse to create controller and listener + testChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + + ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered"; + localListenerCopy = registeredListener; + } + ASSERT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered"; + + gui::WindowInfo windowInfo; + windowInfo.displayId = DISPLAY_ID; + windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY; + gui::DisplayInfo displayInfo; + displayInfo.displayId = DISPLAY_ID; + localListenerCopy->onWindowInfosChanged( + /*windowInfosUpdate=*/{{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0}); +} + } // namespace android -- GitLab From 4a466641791cb573f2d029f283ce8a2b25974bd5 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Thu, 23 May 2024 16:22:22 -0400 Subject: [PATCH 392/465] Add graphite_renderengine_compile_only to SF flags This will be used to guard compilation of Graphite in RenderEngine. Enabling the flag will ONLY compile Graphite, but not enable it. The debug.renderengine.graphite system property could then be set to true to enable Graphite in RenderEngine. The existing graphite_renderengine flag will both compile and enable Graphite in RenderEngine. Compilation is gated on a logical inclusive OR of the two flags. Bug: b/293371537 Bug: b/331678326 Bug: b/341728634 Test: compiles Change-Id: I4d6408b5e65b8b8862c79086dba2f4ce56a3d179 --- services/surfaceflinger/common/FlagManager.cpp | 2 ++ .../surfaceflinger/common/include/common/FlagManager.h | 1 + services/surfaceflinger/surfaceflinger_flags.aconfig | 2 +- services/surfaceflinger/surfaceflinger_flags_new.aconfig | 8 ++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index b78809a59a..b7ec6e0b26 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -149,6 +149,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(local_tonemap_screenshots); DUMP_READ_ONLY_FLAG(override_trusted_overlay); DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache); + DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -248,6 +249,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, ""); FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots"); FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, ""); FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, ""); +FLAG_MANAGER_READ_ONLY_FLAG(force_compile_graphite_renderengine, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 302d311bb9..8f98ed3777 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -88,6 +88,7 @@ public: bool local_tonemap_screenshots() const; bool override_trusted_overlay() const; bool flush_buffer_slots_to_uncache() const; + bool force_compile_graphite_renderengine() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig index dea74d0de6..56bca7fb18 100644 --- a/services/surfaceflinger/surfaceflinger_flags.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags.aconfig @@ -161,7 +161,7 @@ flag { flag { name: "graphite_renderengine" namespace: "core_graphics" - description: "Use Skia's Graphite Vulkan backend in RenderEngine." + description: "Compile AND enable Skia's Graphite Vulkan backend in RenderEngine. See also: force_compile_graphite_renderengine." bug: "293371537" is_fixed_read_only: true } diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index 07728912f1..ee12325705 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -65,6 +65,14 @@ flag { } } # flush_buffer_slots_to_uncache +flag { + name: "force_compile_graphite_renderengine" + namespace: "core_graphics" + description: "Compile Skia's Graphite Vulkan backend in RenderEngine, but do NOT enable it, unless graphite_renderengine is also set. It can also be enabled with the debug.renderengine.graphite system property for testing. In contrast, the graphite_renderengine flag both compiles AND enables Graphite in RenderEngine." + bug: "293371537" + is_fixed_read_only: true +} # force_compile_graphite_renderengine + flag { name: "frame_rate_category_mrr" namespace: "core_graphics" -- GitLab From 488101bf891623780e2f4d0eaacadc6d02e268f9 Mon Sep 17 00:00:00 2001 From: Nolan Scobie Date: Mon, 20 May 2024 13:32:13 -0400 Subject: [PATCH 393/465] [RenderEngine] Only compile Graphite if either Graphite flag is enabled RE-Graphite will be *compiled* if either the graphite_renderengine or force_compile_graphite_renderengine ready-only flag is enabled at compile time (inclusive OR). RE-Graphite can be compiled and *enabled* by: 1. setting just graphite_renderengine=true, or 2. setting both force_compile_graphite_renderengine=true and the debug.renderengine.graphite system property to true. Why RenderEngine needs a dep on libsurfaceflingerflags now: libsurfaceflinger_common pulls in SF's FlagManager, which is layered on top of the API that's autogenerated for SF's aconfig module, libsurfaceflingerflags. The COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(...) macro is defined by com_android_graphics_surfaceflinger_flags.h in that latter aconfig module, and that header is not exported by the higher-level FlagManager code. Bug: b/293371537 Bug: b/331678326 Bug: b/341728634 Test: boot and bloaty (see b/331678326) Flag: com.android.graphics.surfaceflinger.flags.graphite_renderengine Change-Id: I9d0b15bda08430552716a9a1f7cd59d91ee7b9a6 --- libs/renderengine/Android.bp | 1 + libs/renderengine/RenderEngine.cpp | 31 ++++++++++++++++++-- libs/renderengine/tests/Android.bp | 1 + libs/renderengine/tests/RenderEngineTest.cpp | 21 +++++++++++-- services/surfaceflinger/Android.bp | 1 + services/surfaceflinger/SurfaceFlinger.cpp | 17 +++++++++++ 6 files changed, 67 insertions(+), 5 deletions(-) diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp index 757d935647..4a04467308 100644 --- a/libs/renderengine/Android.bp +++ b/libs/renderengine/Android.bp @@ -50,6 +50,7 @@ cc_defaults { "libshaders", "libtonemap", "libsurfaceflinger_common", + "libsurfaceflingerflags", ], local_include_dirs: ["include"], export_include_dirs: ["include"], diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp index 1c60563b25..bc3976d9f1 100644 --- a/libs/renderengine/RenderEngine.cpp +++ b/libs/renderengine/RenderEngine.cpp @@ -22,24 +22,49 @@ #include "skia/SkiaGLRenderEngine.h" #include "threaded/RenderEngineThreaded.h" +#include #include #include +// TODO: b/341728634 - Clean up conditional compilation. +#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE) || \ + COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(FORCE_COMPILE_GRAPHITE_RENDERENGINE) +#define COMPILE_GRAPHITE_RENDERENGINE 1 +#else +#define COMPILE_GRAPHITE_RENDERENGINE 0 +#endif + namespace android { namespace renderengine { std::unique_ptr RenderEngine::create(const RenderEngineCreationArgs& args) { threaded::CreateInstanceFactory createInstanceFactory; +// TODO: b/341728634 - Clean up conditional compilation. +#if COMPILE_GRAPHITE_RENDERENGINE + const RenderEngine::SkiaBackend actualSkiaBackend = args.skiaBackend; +#else + if (args.skiaBackend == RenderEngine::SkiaBackend::GRAPHITE) { + ALOGE("RenderEngine with Graphite Skia backend was requested, but Graphite was not " + "included in the build. Falling back to Ganesh (%s)", + args.graphicsApi == RenderEngine::GraphicsApi::GL ? "GL" : "Vulkan"); + } + const RenderEngine::SkiaBackend actualSkiaBackend = RenderEngine::SkiaBackend::GANESH; +#endif + ALOGD("%sRenderEngine with %s Backend (%s)", args.threaded == Threaded::YES ? "Threaded " : "", args.graphicsApi == GraphicsApi::GL ? "SkiaGL" : "SkiaVK", - args.skiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite"); + actualSkiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite"); - if (args.skiaBackend == SkiaBackend::GRAPHITE) { +// TODO: b/341728634 - Clean up conditional compilation. +#if COMPILE_GRAPHITE_RENDERENGINE + if (actualSkiaBackend == SkiaBackend::GRAPHITE) { createInstanceFactory = [args]() { return android::renderengine::skia::GraphiteVkRenderEngine::create(args); }; - } else { // GANESH + } else +#endif + { // GANESH if (args.graphicsApi == GraphicsApi::VK) { createInstanceFactory = [args]() { return android::renderengine::skia::GaneshVkRenderEngine::create(args); diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index 0eea187407..06d543961b 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -46,6 +46,7 @@ cc_test { "libshaders", "libtonemap", "libsurfaceflinger_common", + "libsurfaceflingerflags", ], header_libs: [ "libtonemap_headers", diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp index 4dcaff9ec8..a8a98236e2 100644 --- a/libs/renderengine/tests/RenderEngineTest.cpp +++ b/libs/renderengine/tests/RenderEngineTest.cpp @@ -22,6 +22,7 @@ #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wextra" +#include #include #include #include @@ -41,6 +42,14 @@ #include "../skia/SkiaVkRenderEngine.h" #include "../threaded/RenderEngineThreaded.h" +// TODO: b/341728634 - Clean up conditional compilation. +#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE) || \ + COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(FORCE_COMPILE_GRAPHITE_RENDERENGINE) +#define COMPILE_GRAPHITE_RENDERENGINE 1 +#else +#define COMPILE_GRAPHITE_RENDERENGINE 0 +#endif + constexpr int DEFAULT_DISPLAY_WIDTH = 128; constexpr int DEFAULT_DISPLAY_HEIGHT = 256; constexpr int DEFAULT_DISPLAY_OFFSET = 64; @@ -152,6 +161,8 @@ public: } }; +// TODO: b/341728634 - Clean up conditional compilation. +#if COMPILE_GRAPHITE_RENDERENGINE class GraphiteVkRenderEngineFactory : public RenderEngineFactory { public: std::string name() override { return "GraphiteVkRenderEngineFactory"; } @@ -164,6 +175,7 @@ public: return renderengine::RenderEngine::SkiaBackend::GRAPHITE; } }; +#endif class RenderEngineTest : public ::testing::TestWithParam> { public: @@ -1497,10 +1509,15 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function(), - std::make_shared(), - std::make_shared())); + std::make_shared() +#if COMPILE_GRAPHITE_RENDERENGINE + , + std::make_shared() +#endif + )); TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) { if (!GetParam()->apiSupported()) { diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 8ca796e93d..4455383367 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -98,6 +98,7 @@ cc_defaults { "libscheduler", "libserviceutils", "libshaders", + "libsurfaceflingerflags", "libtimestats", "libtonemap", ], diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f81cd45cc..be0c1548d7 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -813,8 +814,24 @@ void chooseRenderEngineType(renderengine::RenderEngineCreationArgs::Builder& bui .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK); } else { const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK; +// TODO: b/341728634 - Clean up conditional compilation. +// Note: this guard in particular must check e.g. +// COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE directly (instead of calling e.g. +// COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE)) because that macro is undefined +// in the libsurfaceflingerflags_test variant of com_android_graphics_surfaceflinger_flags.h, which +// is used by layertracegenerator (which also needs SurfaceFlinger.cpp). :) +#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE || \ + COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_FORCE_COMPILE_GRAPHITE_RENDERENGINE const bool useGraphite = FlagManager::getInstance().graphite_renderengine() && renderengine::RenderEngine::canSupport(kVulkan); +#else + const bool useGraphite = false; + if (FlagManager::getInstance().graphite_renderengine()) { + ALOGE("RenderEngine's Graphite Skia backend was requested with the " + "debug.renderengine.graphite system property, but it is not compiled in this " + "build! Falling back to Ganesh backend selection logic."); + } +#endif const bool useVulkan = useGraphite || (FlagManager::getInstance().vulkan_renderengine() && renderengine::RenderEngine::canSupport(kVulkan)); -- GitLab From 09b2388b8c1da1195675d91084a6215f2916d6be Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Tue, 28 May 2024 16:29:06 +0900 Subject: [PATCH 394/465] Apply input resampling for motion events of ToolType::MOUSE and STYLUS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As the resampling is performed for better scrolling, even when a user is dragging with a mouse or a stylus, we'd like to do the same as touch inputs. Also, when an event being resampled has any pointer with a tool type that we don’t resample, the resampled event has an old coordinate but a new timestamp. This event is confusing. With this change, when the event has such a pointer, events won’t be resampled. Bug: 337849873 Test: Enable debug and resampling happens for mouse events. Test: TouchResamplingTest Change-Id: I9344759e28685a08e233939761f93f0b3911413c --- libs/input/InputConsumer.cpp | 44 +++++++----- libs/input/tests/TouchResampling_test.cpp | 85 +++++++++++++++++++++-- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp index abc039281a..fcf490d5f9 100644 --- a/libs/input/InputConsumer.cpp +++ b/libs/input/InputConsumer.cpp @@ -181,7 +181,8 @@ inline bool isPointerEvent(int32_t source) { } bool shouldResampleTool(ToolType toolType) { - return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN; + return toolType == ToolType::FINGER || toolType == ToolType::MOUSE || + toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN; } } // namespace @@ -592,6 +593,11 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id); return; } + if (!shouldResampleTool(event->getToolType(i))) { + ALOGD_IF(debugResampling(), + "Not resampled, containing unsupported tool type at pointer %d", id); + return; + } } // Find the data to use for resampling. @@ -639,10 +645,18 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, } if (current->eventTime == sampleTime) { - // Prevents having 2 events with identical times and coordinates. + ALOGD_IF(debugResampling(), "Not resampled, 2 events with identical times."); return; } + for (size_t i = 0; i < pointerCount; i++) { + uint32_t id = event->getPointerId(i); + if (!other->idBits.hasBit(id)) { + ALOGD_IF(debugResampling(), "Not resampled, the other doesn't have pointer id %d.", id); + return; + } + } + // Resample touch coordinates. History oldLastResample; oldLastResample.initializeFrom(touchState.lastResample); @@ -670,22 +684,16 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, const PointerCoords& currentCoords = current->getPointerById(id); resampledCoords = currentCoords; resampledCoords.isResampled = true; - if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) { - const PointerCoords& otherCoords = other->getPointerById(id); - resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, - lerp(currentCoords.getX(), otherCoords.getX(), alpha)); - resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, - lerp(currentCoords.getY(), otherCoords.getY(), alpha)); - ALOGD_IF(debugResampling(), - "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " - "other (%0.3f, %0.3f), alpha %0.3f", - id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), - currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha); - } else { - ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id, - resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), - currentCoords.getY()); - } + const PointerCoords& otherCoords = other->getPointerById(id); + resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, + lerp(currentCoords.getX(), otherCoords.getX(), alpha)); + resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, + lerp(currentCoords.getY(), otherCoords.getY(), alpha)); + ALOGD_IF(debugResampling(), + "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " + "other (%0.3f, %0.3f), alpha %0.3f", + id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(), + currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha); } event->addSample(sampleTime, touchState.lastResample.pointers); diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp index 2dc9fdb7f1..8d8b5300c1 100644 --- a/libs/input/tests/TouchResampling_test.cpp +++ b/libs/input/tests/TouchResampling_test.cpp @@ -297,10 +297,9 @@ TEST_F(TouchResamplingTest, EventIsResampledWithDifferentId) { } /** - * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with - * a resampled timestamp and should be marked as such. + * Stylus pointer coordinates are resampled. */ -TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) { +TEST_F(TouchResamplingTest, StylusEventIsResampled) { std::chrono::nanoseconds frameTime; std::vector entries, expectedEntries; @@ -330,14 +329,90 @@ TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) { // id x y {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE}, {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE}, - // A resampled event is generated, but the stylus coordinates are not resampled. {25ms, - {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}}, + {{0, 35, 30, .toolType = ToolType::STYLUS, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE}, }; consumeInputEventEntries(expectedEntries, frameTime); } +/** + * Mouse pointer coordinates are resampled. + */ +TEST_F(TouchResamplingTest, MouseEventIsResampled) { + std::chrono::nanoseconds frameTime; + std::vector entries, expectedEntries; + + // Initial ACTION_DOWN should be separate, because the first consume event will only return + // InputEvent with a single action. + entries = { + // id x y + {0ms, {{0, 10, 20, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 10, 20, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_DOWN}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + entries = { + // id x y + {10ms, {{0, 20, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 35ms; + expectedEntries = { + // id x y + {10ms, {{0, 20, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE}, + {25ms, + {{0, 35, 30, .toolType = ToolType::MOUSE, .isResampled = true}}, + AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + +/** + * Motion events with palm tool type are not resampled. + */ +TEST_F(TouchResamplingTest, PalmEventIsNotResampled) { + std::chrono::nanoseconds frameTime; + std::vector entries, expectedEntries; + + // Initial ACTION_DOWN should be separate, because the first consume event will only return + // InputEvent with a single action. + entries = { + // id x y + {0ms, {{0, 10, 20, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_DOWN}, + }; + publishInputEventEntries(entries); + frameTime = 5ms; + expectedEntries = { + // id x y + {0ms, {{0, 10, 20, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_DOWN}, + }; + consumeInputEventEntries(expectedEntries, frameTime); + + // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y + entries = { + // id x y + {10ms, {{0, 20, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE}, + }; + publishInputEventEntries(entries); + frameTime = 35ms; + expectedEntries = { + // id x y + {10ms, {{0, 20, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE}, + {20ms, {{0, 30, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE}, + }; + consumeInputEventEntries(expectedEntries, frameTime); +} + /** * Event should not be resampled when sample time is equal to event time. */ -- GitLab From 71953c2b88a4d34488b17b3a1fe3758f6960bd54 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Mon, 3 Jun 2024 12:54:40 +0000 Subject: [PATCH 395/465] input accumulators: pass RawEvent references instead of pointers Change-Id: I7f538b3e08704df7c99e13e59302832dfe517a1d Test: atest inputflinger_test Test: manually test a multi-touch screen, touchpad, mouse, and drawing tablet works Bug: 245989146 Flag: EXEMPT refactor --- .../mapper/CapturedTouchpadEventConverter.cpp | 4 +- .../reader/mapper/CursorInputMapper.cpp | 16 ++++---- .../reader/mapper/CursorInputMapper.h | 2 +- .../mapper/ExternalStylusInputMapper.cpp | 4 +- .../reader/mapper/MultiTouchInputMapper.cpp | 2 +- .../mapper/RotaryEncoderInputMapper.cpp | 2 +- .../reader/mapper/SingleTouchInputMapper.cpp | 2 +- .../reader/mapper/TouchInputMapper.cpp | 6 +-- .../reader/mapper/TouchpadInputMapper.cpp | 2 +- .../accumulator/CursorButtonAccumulator.cpp | 22 +++++----- .../accumulator/CursorButtonAccumulator.h | 2 +- .../accumulator/CursorScrollAccumulator.cpp | 10 ++--- .../accumulator/CursorScrollAccumulator.h | 2 +- .../MultiTouchMotionAccumulator.cpp | 16 ++++---- .../accumulator/MultiTouchMotionAccumulator.h | 2 +- .../SingleTouchMotionAccumulator.cpp | 20 +++++----- .../SingleTouchMotionAccumulator.h | 2 +- .../accumulator/TouchButtonAccumulator.cpp | 40 +++++++++---------- .../accumulator/TouchButtonAccumulator.h | 2 +- .../gestures/HardwareStateConverter.cpp | 10 ++--- .../mapper/gestures/HardwareStateConverter.h | 2 +- .../tests/HardwareStateConverter_test.cpp | 4 +- .../MultiTouchMotionAccumulator_test.cpp | 2 +- 23 files changed, 88 insertions(+), 88 deletions(-) diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp index 09a5f08a23..90685dec2b 100644 --- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp +++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp @@ -155,8 +155,8 @@ std::list CapturedTouchpadEventConverter::process(const RawEvent& ra mMotionAccumulator.finishSync(); } - mCursorButtonAccumulator.process(&rawEvent); - mMotionAccumulator.process(&rawEvent); + mCursorButtonAccumulator.process(rawEvent); + mMotionAccumulator.process(rawEvent); return out; } diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index c67314dbcf..90de74564c 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -55,14 +55,14 @@ void CursorMotionAccumulator::clearRelativeAxes() { mRelY = 0; } -void CursorMotionAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_REL) { - switch (rawEvent->code) { +void CursorMotionAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_REL) { + switch (rawEvent.code) { case REL_X: - mRelX = rawEvent->value; + mRelX = rawEvent.value; break; case REL_Y: - mRelY = rawEvent->value; + mRelY = rawEvent.value; break; } } @@ -217,9 +217,9 @@ std::list CursorInputMapper::reset(nsecs_t when) { std::list CursorInputMapper::process(const RawEvent* rawEvent) { std::list out; - mCursorButtonAccumulator.process(rawEvent); - mCursorMotionAccumulator.process(rawEvent); - mCursorScrollAccumulator.process(rawEvent); + mCursorButtonAccumulator.process(*rawEvent); + mCursorMotionAccumulator.process(*rawEvent); + mCursorScrollAccumulator.process(*rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { const auto [eventTime, readTime] = diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 75ca9c00a8..5bde32b1c0 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -34,7 +34,7 @@ public: CursorMotionAccumulator(); void reset(InputDeviceContext& deviceContext); - void process(const RawEvent* rawEvent); + void process(const RawEvent& rawEvent); void finishSync(); inline int32_t getRelativeX() const { return mRelX; } diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 987d2d0221..7f7e0dd9e5 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -63,8 +63,8 @@ std::list ExternalStylusInputMapper::reset(nsecs_t when) { std::list ExternalStylusInputMapper::process(const RawEvent* rawEvent) { std::list out; - mSingleTouchMotionAccumulator.process(rawEvent); - mTouchButtonAccumulator.process(rawEvent); + mSingleTouchMotionAccumulator.process(*rawEvent); + mTouchButtonAccumulator.process(*rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { out += sync(rawEvent->when); diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index bca9d9183b..05ffa4d11f 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -43,7 +43,7 @@ std::list MultiTouchInputMapper::reset(nsecs_t when) { std::list MultiTouchInputMapper::process(const RawEvent* rawEvent) { std::list out = TouchInputMapper::process(rawEvent); - mMultiTouchMotionAccumulator.process(rawEvent); + mMultiTouchMotionAccumulator.process(*rawEvent); return out; } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 0ddbc06890..182e1b715e 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -103,7 +103,7 @@ std::list RotaryEncoderInputMapper::reset(nsecs_t when) { std::list RotaryEncoderInputMapper::process(const RawEvent* rawEvent) { std::list out; - mRotaryEncoderScrollAccumulator.process(rawEvent); + mRotaryEncoderScrollAccumulator.process(*rawEvent); if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { out += sync(rawEvent->when, rawEvent->readTime); diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp index ed0e27067f..b7365d3d13 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp @@ -33,7 +33,7 @@ std::list SingleTouchInputMapper::reset(nsecs_t when) { std::list SingleTouchInputMapper::process(const RawEvent* rawEvent) { std::list out = TouchInputMapper::process(rawEvent); - mSingleTouchMotionAccumulator.process(rawEvent); + mSingleTouchMotionAccumulator.process(*rawEvent); return out; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 8b4b691a45..1e9f28a19d 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1416,9 +1416,9 @@ void TouchInputMapper::clearStylusDataPendingFlags() { } std::list TouchInputMapper::process(const RawEvent* rawEvent) { - mCursorButtonAccumulator.process(rawEvent); - mCursorScrollAccumulator.process(rawEvent); - mTouchButtonAccumulator.process(rawEvent); + mCursorButtonAccumulator.process(*rawEvent); + mCursorScrollAccumulator.process(*rawEvent); + mTouchButtonAccumulator.process(*rawEvent); std::list out; if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index daf99dae87..a97d646e3a 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -424,7 +424,7 @@ std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { if (mMotionAccumulator.getActiveSlotsCount() == 0) { mGestureStartTime = rawEvent->when; } - std::optional state = mStateConverter.processRawEvent(rawEvent); + std::optional state = mStateConverter.processRawEvent(*rawEvent); if (state) { updatePalmDetectionMetrics(); return sendHardwareState(rawEvent->when, rawEvent->readTime, *state); diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp index 153236c177..9e722d41e7 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp @@ -47,32 +47,32 @@ void CursorButtonAccumulator::clearButtons() { mBtnTask = 0; } -void CursorButtonAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_KEY) { - switch (rawEvent->code) { +void CursorButtonAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_KEY) { + switch (rawEvent.code) { case BTN_LEFT: - mBtnLeft = rawEvent->value; + mBtnLeft = rawEvent.value; break; case BTN_RIGHT: - mBtnRight = rawEvent->value; + mBtnRight = rawEvent.value; break; case BTN_MIDDLE: - mBtnMiddle = rawEvent->value; + mBtnMiddle = rawEvent.value; break; case BTN_BACK: - mBtnBack = rawEvent->value; + mBtnBack = rawEvent.value; break; case BTN_SIDE: - mBtnSide = rawEvent->value; + mBtnSide = rawEvent.value; break; case BTN_FORWARD: - mBtnForward = rawEvent->value; + mBtnForward = rawEvent.value; break; case BTN_EXTRA: - mBtnExtra = rawEvent->value; + mBtnExtra = rawEvent.value; break; case BTN_TASK: - mBtnTask = rawEvent->value; + mBtnTask = rawEvent.value; break; } } diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h index 6960644a1d..256b2bb994 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h @@ -29,7 +29,7 @@ public: CursorButtonAccumulator(); void reset(const InputDeviceContext& deviceContext); - void process(const RawEvent* rawEvent); + void process(const RawEvent& rawEvent); uint32_t getButtonState() const; inline bool isLeftPressed() const { return mBtnLeft; } diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp index 07146941fd..f85cab205b 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp @@ -39,14 +39,14 @@ void CursorScrollAccumulator::clearRelativeAxes() { mRelHWheel = 0; } -void CursorScrollAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_REL) { - switch (rawEvent->code) { +void CursorScrollAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_REL) { + switch (rawEvent.code) { case REL_WHEEL: - mRelWheel = rawEvent->value; + mRelWheel = rawEvent.value; break; case REL_HWHEEL: - mRelHWheel = rawEvent->value; + mRelHWheel = rawEvent.value; break; } } diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h index ae1b7a32f4..e563620252 100644 --- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h @@ -31,7 +31,7 @@ public: void configure(InputDeviceContext& deviceContext); void reset(InputDeviceContext& deviceContext); - void process(const RawEvent* rawEvent); + void process(const RawEvent& rawEvent); void finishSync(); inline bool haveRelativeVWheel() const { return mHaveRelWheel; } diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp index b3f170075c..4919068201 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp @@ -45,12 +45,12 @@ void MultiTouchMotionAccumulator::resetSlots() { mCurrentSlot = -1; } -void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_ABS) { +void MultiTouchMotionAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_ABS) { bool newSlot = false; if (mUsingSlotsProtocol) { - if (rawEvent->code == ABS_MT_SLOT) { - mCurrentSlot = rawEvent->value; + if (rawEvent.code == ABS_MT_SLOT) { + mCurrentSlot = rawEvent.value; newSlot = true; } } else if (mCurrentSlot < 0) { @@ -72,12 +72,12 @@ void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { if (!mUsingSlotsProtocol) { slot.mInUse = true; } - if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) { - warnIfNotInUse(*rawEvent, slot); + if (rawEvent.code == ABS_MT_POSITION_X || rawEvent.code == ABS_MT_POSITION_Y) { + warnIfNotInUse(rawEvent, slot); } - slot.populateAxisValue(rawEvent->code, rawEvent->value); + slot.populateAxisValue(rawEvent.code, rawEvent.value); } - } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { + } else if (rawEvent.type == EV_SYN && rawEvent.code == SYN_MT_REPORT) { // MultiTouch Sync: The driver has returned all data for *one* of the pointers. mCurrentSlot += 1; } diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h index a0f21470c4..388ed82373 100644 --- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h @@ -76,7 +76,7 @@ public: void configure(const InputDeviceContext& deviceContext, size_t slotCount, bool usingSlotsProtocol); void reset(const InputDeviceContext& deviceContext); - void process(const RawEvent* rawEvent); + void process(const RawEvent& rawEvent); void finishSync(); size_t getActiveSlotsCount() const; diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp index 27b8e40fc6..2b82ddf33d 100644 --- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp @@ -45,29 +45,29 @@ void SingleTouchMotionAccumulator::clearAbsoluteAxes() { mAbsTiltY = 0; } -void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) { - if (rawEvent->type == EV_ABS) { - switch (rawEvent->code) { +void SingleTouchMotionAccumulator::process(const RawEvent& rawEvent) { + if (rawEvent.type == EV_ABS) { + switch (rawEvent.code) { case ABS_X: - mAbsX = rawEvent->value; + mAbsX = rawEvent.value; break; case ABS_Y: - mAbsY = rawEvent->value; + mAbsY = rawEvent.value; break; case ABS_PRESSURE: - mAbsPressure = rawEvent->value; + mAbsPressure = rawEvent.value; break; case ABS_TOOL_WIDTH: - mAbsToolWidth = rawEvent->value; + mAbsToolWidth = rawEvent.value; break; case ABS_DISTANCE: - mAbsDistance = rawEvent->value; + mAbsDistance = rawEvent.value; break; case ABS_TILT_X: - mAbsTiltX = rawEvent->value; + mAbsTiltX = rawEvent.value; break; case ABS_TILT_Y: - mAbsTiltY = rawEvent->value; + mAbsTiltY = rawEvent.value; break; } } diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h index 93056f06e6..fb74bcaf4c 100644 --- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h @@ -28,7 +28,7 @@ class SingleTouchMotionAccumulator { public: SingleTouchMotionAccumulator(); - void process(const RawEvent* rawEvent); + void process(const RawEvent& rawEvent); void reset(InputDeviceContext& deviceContext); inline int32_t getAbsoluteX() const { return mAbsX; } diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp index 8c4bed3267..ba8577e462 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp @@ -52,60 +52,60 @@ void TouchButtonAccumulator::reset() { mHidUsageAccumulator.reset(); } -void TouchButtonAccumulator::process(const RawEvent* rawEvent) { - mHidUsageAccumulator.process(*rawEvent); +void TouchButtonAccumulator::process(const RawEvent& rawEvent) { + mHidUsageAccumulator.process(rawEvent); - if (rawEvent->type == EV_KEY) { - switch (rawEvent->code) { + if (rawEvent.type == EV_KEY) { + switch (rawEvent.code) { case BTN_TOUCH: - mBtnTouch = rawEvent->value; + mBtnTouch = rawEvent.value; break; case BTN_STYLUS: - mBtnStylus = rawEvent->value; + mBtnStylus = rawEvent.value; break; case BTN_STYLUS2: case BTN_0: // BTN_0 is what gets mapped for the HID usage // Digitizers.SecondaryBarrelSwitch - mBtnStylus2 = rawEvent->value; + mBtnStylus2 = rawEvent.value; break; case BTN_TOOL_FINGER: - mBtnToolFinger = rawEvent->value; + mBtnToolFinger = rawEvent.value; break; case BTN_TOOL_PEN: - mBtnToolPen = rawEvent->value; + mBtnToolPen = rawEvent.value; break; case BTN_TOOL_RUBBER: - mBtnToolRubber = rawEvent->value; + mBtnToolRubber = rawEvent.value; break; case BTN_TOOL_BRUSH: - mBtnToolBrush = rawEvent->value; + mBtnToolBrush = rawEvent.value; break; case BTN_TOOL_PENCIL: - mBtnToolPencil = rawEvent->value; + mBtnToolPencil = rawEvent.value; break; case BTN_TOOL_AIRBRUSH: - mBtnToolAirbrush = rawEvent->value; + mBtnToolAirbrush = rawEvent.value; break; case BTN_TOOL_MOUSE: - mBtnToolMouse = rawEvent->value; + mBtnToolMouse = rawEvent.value; break; case BTN_TOOL_LENS: - mBtnToolLens = rawEvent->value; + mBtnToolLens = rawEvent.value; break; case BTN_TOOL_DOUBLETAP: - mBtnToolDoubleTap = rawEvent->value; + mBtnToolDoubleTap = rawEvent.value; break; case BTN_TOOL_TRIPLETAP: - mBtnToolTripleTap = rawEvent->value; + mBtnToolTripleTap = rawEvent.value; break; case BTN_TOOL_QUADTAP: - mBtnToolQuadTap = rawEvent->value; + mBtnToolQuadTap = rawEvent.value; break; case BTN_TOOL_QUINTTAP: - mBtnToolQuintTap = rawEvent->value; + mBtnToolQuintTap = rawEvent.value; break; default: - processMappedKey(rawEvent->code, rawEvent->value); + processMappedKey(rawEvent.code, rawEvent.value); } return; } diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h index e829692206..c7adf8448e 100644 --- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h +++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h @@ -33,7 +33,7 @@ public: void configure(); void reset(); - void process(const RawEvent* rawEvent); + void process(const RawEvent& rawEvent); uint32_t getButtonState() const; ToolType getToolType() const; diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp index b89b7f38a9..6885adb242 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp @@ -40,15 +40,15 @@ HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceC } std::optional HardwareStateConverter::processRawEvent( - const RawEvent* rawEvent) { + const RawEvent& rawEvent) { std::optional out; - if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out = produceHardwareState(rawEvent->when); + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + out = produceHardwareState(rawEvent.when); mMotionAccumulator.finishSync(); mMscTimestamp = 0; } - if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) { - mMscTimestamp = rawEvent->value; + if (rawEvent.type == EV_MSC && rawEvent.code == MSC_TIMESTAMP) { + mMscTimestamp = rawEvent.value; } mCursorButtonAccumulator.process(rawEvent); mMotionAccumulator.process(rawEvent); diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h index 633448e67e..07e62c6eba 100644 --- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h +++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h @@ -44,7 +44,7 @@ public: HardwareStateConverter(const InputDeviceContext& deviceContext, MultiTouchMotionAccumulator& motionAccumulator); - std::optional processRawEvent(const RawEvent* event); + std::optional processRawEvent(const RawEvent& event); void reset(); private: diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp index ff9bd9e75b..34c81fcf02 100644 --- a/services/inputflinger/tests/HardwareStateConverter_test.cpp +++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp @@ -81,7 +81,7 @@ protected: event.type = type; event.code = code; event.value = value; - std::optional schs = mConverter->processRawEvent(&event); + std::optional schs = mConverter->processRawEvent(event); EXPECT_FALSE(schs.has_value()); } @@ -93,7 +93,7 @@ protected: event.type = EV_SYN; event.code = SYN_REPORT; event.value = 0; - return mConverter->processRawEvent(&event); + return mConverter->processRawEvent(event); } std::shared_ptr mFakeEventHub; diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp index 5e67506b48..b441a23803 100644 --- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp +++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp @@ -38,7 +38,7 @@ protected: event.type = type; event.code = code; event.value = value; - mMotionAccumulator.process(&event); + mMotionAccumulator.process(event); } }; -- GitLab From 925b0c1880d16541fc22b7f5ded4a4266ab1ed3a Mon Sep 17 00:00:00 2001 From: Kaylee Lubick Date: Mon, 3 Jun 2024 13:59:04 +0000 Subject: [PATCH 396/465] Update deprecated GrVk type references These are just type aliases and Skia is aligning the Vulkan types to be shared between Ganesh and Graphite. Change-Id: I6ab6c6c977310c6c9d40526f5e6761c70e346952 --- libs/renderengine/skia/VulkanInterface.cpp | 6 ++++-- libs/renderengine/skia/VulkanInterface.h | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp index fc16c5626d..5e756b03ed 100644 --- a/libs/renderengine/skia/VulkanInterface.cpp +++ b/libs/renderengine/skia/VulkanInterface.cpp @@ -197,7 +197,9 @@ void VulkanInterface::onVkDeviceFault(void* callbackContext, const std::string& LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str()); }; -static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) { +static skgpu::VulkanGetProc sGetProc = [](const char* proc_name, + VkInstance instance, + VkDevice device) { if (device != VK_NULL_HANDLE) { return vkGetDeviceProcAddr(device, proc_name); } @@ -604,7 +606,7 @@ void VulkanInterface::teardown() { mQueue = VK_NULL_HANDLE; // Implicitly destroyed by destroying mDevice. mQueueIndex = 0; mApiVersion = 0; - mGrExtensions = GrVkExtensions(); + mGrExtensions = skgpu::VulkanExtensions(); mGrGetProc = nullptr; mIsProtected = false; mIsRealtimePriority = false; diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h index af0489a470..f20b00251b 100644 --- a/libs/renderengine/skia/VulkanInterface.h +++ b/libs/renderengine/skia/VulkanInterface.h @@ -17,7 +17,8 @@ #pragma once #include -#include +#include +#include #include @@ -85,12 +86,12 @@ private: VkQueue mQueue = VK_NULL_HANDLE; int mQueueIndex = 0; uint32_t mApiVersion = 0; - GrVkExtensions mGrExtensions; + skgpu::VulkanExtensions mGrExtensions; VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr; VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr; VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr; VkPhysicalDeviceFaultFeaturesEXT* mDeviceFaultFeatures = nullptr; - GrVkGetProc mGrGetProc = nullptr; + skgpu::VulkanGetProc mGrGetProc = nullptr; bool mIsProtected = false; bool mIsRealtimePriority = false; -- GitLab From c28181ed1c8e75cdf5cb5971f0f88b5262890ad7 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Mon, 3 Jun 2024 14:05:54 +0000 Subject: [PATCH 397/465] Refactor PointerChoreographerTest to use Mock Policy We can now use mocked PointerChoreographer policy in choreographer tests. Bug: 344554018 Test: atest inputlfinger_tests Change-Id: I8fb0d0094a9a8ea311122c35f8a9a41ada0c12fa --- .../tests/PointerChoreographer_test.cpp | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index b517928f66..3f2d6ec45c 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -113,17 +113,31 @@ TestPointerChoreographer::TestPointerChoreographer( windowInfoListener = nullptr; }) {} -class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface { +class PointerChoreographerTest : public testing::Test { protected: TestInputListener mTestListener; sp mRegisteredWindowInfoListener; std::vector mInjectedInitialWindowInfos; - TestPointerChoreographer mChoreographer{mTestListener, *this, mRegisteredWindowInfoListener, + testing::NiceMock mMockPolicy; + TestPointerChoreographer mChoreographer{mTestListener, mMockPolicy, + mRegisteredWindowInfoListener, mInjectedInitialWindowInfos}; void SetUp() override { // flag overrides input_flags::hide_pointer_indicators_for_secure_windows(true); + + ON_CALL(mMockPolicy, createPointerController).WillByDefault([this](ControllerType type) { + std::shared_ptr pc = std::make_shared(); + EXPECT_FALSE(pc->isPointerShown()); + mCreatedControllers.emplace_back(type, pc); + return pc; + }); + + ON_CALL(mMockPolicy, notifyPointerDisplayIdChanged) + .WillByDefault([this](ui::LogicalDisplayId displayId, const FloatPoint& position) { + mPointerDisplayIdNotified = displayId; + }); } std::shared_ptr assertPointerControllerCreated( @@ -177,19 +191,6 @@ private: std::deque>> mCreatedControllers; std::optional mPointerDisplayIdNotified; - - std::shared_ptr createPointerController( - ControllerType type) override { - std::shared_ptr pc = std::make_shared(); - EXPECT_FALSE(pc->isPointerShown()); - mCreatedControllers.emplace_back(type, pc); - return pc; - } - - void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, - const FloatPoint& position) override { - mPointerDisplayIdNotified = displayId; - } }; TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) { -- GitLab From a1046a893eba53f2919a3885b5ec4431c0e05bf7 Mon Sep 17 00:00:00 2001 From: Josep del Rio Date: Thu, 24 Aug 2023 19:57:27 +0000 Subject: [PATCH 398/465] Group player leds in Sony Dualsense controller Sony reported that Android will not group the player LEDs properly for the DualSense controller. After investigating, our mechanism to group LEDs would not support the standard name for player ID lights. This fix expands the regular expression so it will support this format in addition of the name that the gamepad driver will use in certain older devices. Bug: 289067916 Flag: EXEMPT bugfix Test: `adb shell dumpsys input` Change-Id: I5b5e3f5e8478422bbc717a6c272a24e6568464fc --- .../controller/PeripheralController.cpp | 6 ++- .../inputflinger/tests/InputReader_test.cpp | 51 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp index 27b9d23ff5..49ad8b5d69 100644 --- a/services/inputflinger/reader/controller/PeripheralController.cpp +++ b/services/inputflinger/reader/controller/PeripheralController.cpp @@ -418,7 +418,11 @@ void PeripheralController::configureLights() { } rawInfos.insert_or_assign(rawId, rawInfo.value()); // Check if this is a group LEDs for player ID - std::regex lightPattern("([a-z]+)([0-9]+)"); + // The name for the light has already been parsed and is the `function` + // value; for player ID lights the function is expected to be `player-#`. + // However, the Sony driver will use `sony#` instead on SIXAXIS + // gamepads. + std::regex lightPattern("(player|sony)-?([0-9]+)"); std::smatch results; if (std::regex_match(rawInfo->name, results, lightPattern)) { std::string commonName = results[1].str(); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 8536ff0676..9960292baf 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -10595,24 +10595,66 @@ TEST_F(LightControllerTest, MultiColorRGBKeyboardBacklight) { ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR); } +TEST_F(LightControllerTest, SonyPlayerIdLight) { + RawLightInfo info1 = {.id = 1, + .name = "sony1", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + RawLightInfo info2 = {.id = 2, + .name = "sony2", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + RawLightInfo info3 = {.id = 3, + .name = "sony3", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + RawLightInfo info4 = {.id = 4, + .name = "sony4", + .maxBrightness = 255, + .flags = InputLightClass::BRIGHTNESS, + .path = ""}; + mFakeEventHub->addRawLightInfo(info1.id, std::move(info1)); + mFakeEventHub->addRawLightInfo(info2.id, std::move(info2)); + mFakeEventHub->addRawLightInfo(info3.id, std::move(info3)); + mFakeEventHub->addRawLightInfo(info4.id, std::move(info4)); + + PeripheralController& controller = addControllerAndConfigure(); + InputDeviceInfo info; + controller.populateDeviceInfo(&info); + std::vector lights = info.getLights(); + ASSERT_EQ(1U, lights.size()); + ASSERT_STREQ("sony", lights[0].name.c_str()); + ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type); + ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); + ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); + + ASSERT_FALSE(controller.setLightColor(lights[0].id, LIGHT_COLOR)); + ASSERT_TRUE(controller.setLightPlayerId(lights[0].id, LIGHT_PLAYER_ID)); + ASSERT_EQ(controller.getLightPlayerId(lights[0].id).value_or(-1), LIGHT_PLAYER_ID); + ASSERT_STREQ("sony", lights[0].name.c_str()); +} + TEST_F(LightControllerTest, PlayerIdLight) { RawLightInfo info1 = {.id = 1, - .name = "player1", + .name = "player-1", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; RawLightInfo info2 = {.id = 2, - .name = "player2", + .name = "player-2", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; RawLightInfo info3 = {.id = 3, - .name = "player3", + .name = "player-3", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; RawLightInfo info4 = {.id = 4, - .name = "player4", + .name = "player-4", .maxBrightness = 255, .flags = InputLightClass::BRIGHTNESS, .path = ""}; @@ -10626,6 +10668,7 @@ TEST_F(LightControllerTest, PlayerIdLight) { controller.populateDeviceInfo(&info); std::vector lights = info.getLights(); ASSERT_EQ(1U, lights.size()); + ASSERT_STREQ("player", lights[0].name.c_str()); ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type); ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)); ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB)); -- GitLab From b09ba1e24c6d6ed763e6976b2300c47f997de899 Mon Sep 17 00:00:00 2001 From: Rocky Fang Date: Mon, 3 Jun 2024 22:42:58 +0000 Subject: [PATCH 399/465] Add flag for tracing Bug: 333132224 Test: presubmit Change-Id: I4f9e96dd4811406d9273d37310e3b415c12928f2 --- services/sensorservice/senserservice_flags.aconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig index 5b499a8fd8..4c1d314c7b 100644 --- a/services/sensorservice/senserservice_flags.aconfig +++ b/services/sensorservice/senserservice_flags.aconfig @@ -27,4 +27,11 @@ flag { namespace: "sensors" description: "When this flag is enabled, sensor service will only erase dynamic sensor data at the end of the threadLoop to prevent race condition." bug: "329020894" +} + +flag { + name: "sensor_service_report_sensor_usage_in_tracing" + namespace: "sensors" + description: "When this flag is enabled, sensor service will report sensor usage when system trace is enabled." + bug: "333132224" } \ No newline at end of file -- GitLab From f12a678ffe5e640bee4b8595a2784feea3d991f3 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 3 Jun 2024 23:08:15 +0000 Subject: [PATCH 400/465] Fix unsafe layer hierarchy access We may modify the layer hierarchy while updating relative layers. Fix this by retrieving the list of descendants and then attaching or detaching it from their relative parent. Test: presubmit Fixes: 344113039 Flag: EXEMPT bugfixes Change-Id: I7aff5085794aeeb0bbba90decaf4cec13d0d2485 --- .../FrontEnd/LayerHierarchy.cpp | 41 +++++++++++-------- .../surfaceflinger/FrontEnd/LayerHierarchy.h | 1 + 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp index 2b20de38d7..39a6b777bb 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp @@ -260,27 +260,36 @@ void LayerHierarchyBuilder::detachFromRelativeParent(LayerHierarchy* hierarchy) hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Attached); } -void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) { - if (root->mLayer) { - attachToRelativeParent(root); - } - for (auto& [child, childVariant] : root->mChildren) { - if (childVariant == LayerHierarchy::Variant::Detached || - childVariant == LayerHierarchy::Variant::Attached) { - attachHierarchyToRelativeParent(child); +std::vector LayerHierarchyBuilder::getDescendants(LayerHierarchy* root) { + std::vector hierarchies; + hierarchies.push_back(root); + std::vector descendants; + for (size_t i = 0; i < hierarchies.size(); i++) { + LayerHierarchy* hierarchy = hierarchies[i]; + if (hierarchy->mLayer) { + descendants.push_back(hierarchy); + } + for (auto& [child, childVariant] : hierarchy->mChildren) { + if (childVariant == LayerHierarchy::Variant::Detached || + childVariant == LayerHierarchy::Variant::Attached) { + hierarchies.push_back(child); + } } } + return descendants; } -void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) { - if (root->mLayer) { - detachFromRelativeParent(root); +void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) { + std::vector hierarchiesToAttach = getDescendants(root); + for (LayerHierarchy* hierarchy : hierarchiesToAttach) { + attachToRelativeParent(hierarchy); } - for (auto& [child, childVariant] : root->mChildren) { - if (childVariant == LayerHierarchy::Variant::Detached || - childVariant == LayerHierarchy::Variant::Attached) { - detachHierarchyFromRelativeParent(child); - } +} + +void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) { + std::vector hierarchiesToDetach = getDescendants(root); + for (LayerHierarchy* hierarchy : hierarchiesToDetach) { + detachFromRelativeParent(hierarchy); } } diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h index 69710be8df..d023f9e9f5 100644 --- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h +++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h @@ -218,6 +218,7 @@ private: void detachFromParent(LayerHierarchy*); void attachToRelativeParent(LayerHierarchy*); void detachFromRelativeParent(LayerHierarchy*); + std::vector getDescendants(LayerHierarchy*); void attachHierarchyToRelativeParent(LayerHierarchy*); void detachHierarchyFromRelativeParent(LayerHierarchy*); void init(const std::vector>&); -- GitLab From 335ef3e2055cd712d5423c41f2add507dab7d9a2 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Fri, 31 May 2024 22:58:40 +0000 Subject: [PATCH 401/465] Refactor obtaining display in SF main thread Getting the display requires the SF state lock, and this can cause deadlocks if this work is done on a binder thread. The eventual goal is to place all the work in renderScreenImpl on the binder thread, so reshuffling where we get the display to an area where we already go onto the main thread will reduce lock contention. Additional cleanups to remove overloaded renderScreenImpl for testable SF suite. Bug: b/294936197 Test: atest SurfaceFlinger_test Test: atest CompositionTest Test: presubmit Change-Id: I95697a2508a9d936ac9689430d9060c49ed3f039 --- services/surfaceflinger/SurfaceFlinger.cpp | 150 +++++++++--------- services/surfaceflinger/SurfaceFlinger.h | 21 +-- .../tests/unittests/CompositionTest.cpp | 2 +- .../tests/unittests/TestableSurfaceFlinger.h | 17 +- 4 files changed, 97 insertions(+), 93 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5f81cd45cc..d9f7804051 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8220,36 +8220,66 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuil futureFence.get(); } -ftl::SharedFuture SurfaceFlinger::captureScreenshot( - RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshots, - const std::shared_ptr& buffer, bool regionSampling, - bool grayscale, bool isProtected, const sp& captureListener) { - ATRACE_CALL(); - - auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES( - kMainThreadContext) mutable -> ftl::SharedFuture { - // LayerSnapshots must be obtained from the main thread. - auto layers = getLayerSnapshots(); - +const sp SurfaceFlinger::getRenderAreaDisplay( + RenderAreaBuilderVariant& renderAreaBuilder, OutputCompositionState& state) { + sp display = nullptr; + { + Mutex::Autolock lock(mStateLock); if (auto* layerRenderAreaBuilder = std::get_if(&renderAreaBuilder)) { // LayerSnapshotBuilder should only be accessed from the main thread. - frontend::LayerSnapshot* snapshot = + const frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(layerRenderAreaBuilder->layer->getSequence()); if (!snapshot) { ALOGW("Couldn't find layer snapshot for %d", layerRenderAreaBuilder->layer->getSequence()); } else { layerRenderAreaBuilder->setLayerSnapshot(*snapshot); + display = findDisplay( + [layerStack = snapshot->outputFilter.layerStack](const auto& display) { + return display.getLayerStack() == layerStack; + }); } + } else if (auto* displayRenderAreaBuilder = + std::get_if(&renderAreaBuilder)) { + display = displayRenderAreaBuilder->displayWeak.promote(); } - if (FlagManager::getInstance().ce_fence_promise()) { - for (auto& [layer, layerFE] : layers) { - attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); - } + if (display == nullptr) { + display = getDefaultDisplayDeviceLocked(); } + if (display != nullptr) { + state = display->getCompositionDisplay()->getState(); + } + } + return display; +} + +std::vector>> +SurfaceFlinger::getLayerSnapshotsFromMainThread(GetLayerSnapshotsFunction getLayerSnapshotsFn) { + auto layers = getLayerSnapshotsFn(); + if (FlagManager::getInstance().ce_fence_promise()) { + for (auto& [layer, layerFE] : layers) { + attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); + } + } + return layers; +} + +ftl::SharedFuture SurfaceFlinger::captureScreenshot( + RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn, + const std::shared_ptr& buffer, bool regionSampling, + bool grayscale, bool isProtected, const sp& captureListener) { + ATRACE_CALL(); + + auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES( + kMainThreadContext) mutable -> ftl::SharedFuture { + auto layers = getLayerSnapshotsFromMainThread(getLayerSnapshotsFn); + + OutputCompositionState state; + const auto display = getRenderAreaDisplay(renderAreaBuilder, state); + ScreenCaptureResults captureResults; std::unique_ptr renderArea = std::visit([](auto&& arg) -> std::unique_ptr { return arg.build(); }, @@ -8266,7 +8296,7 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( ftl::SharedFuture renderFuture = renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, - isProtected, captureResults, layers); + isProtected, captureResults, display, state, layers); if (captureListener) { // Defer blocking on renderFuture back to the Binder thread. @@ -8295,19 +8325,11 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( return chain.share(); } -ftl::SharedFuture SurfaceFlinger::renderScreenImpl( - std::unique_ptr renderArea, GetLayerSnapshotsFunction getLayerSnapshots, - const std::shared_ptr& buffer, bool regionSampling, - bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) { - auto layers = getLayerSnapshots(); - return renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected, - captureResults, layers); -} - ftl::SharedFuture SurfaceFlinger::renderScreenImpl( std::unique_ptr renderArea, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults, + const sp display, const OutputCompositionState& state, std::vector>>& layers) { ATRACE_CALL(); @@ -8334,60 +8356,36 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() && !renderArea->getHintForSeamlessTransition(); - { - Mutex::Autolock lock(mStateLock); - const DisplayDevice* display = nullptr; - if (parent) { - const frontend::LayerSnapshot* snapshot = - mLayerSnapshotBuilder.getSnapshot(parent->sequence); - if (snapshot) { - display = findDisplay([layerStack = snapshot->outputFilter.layerStack]( - const auto& display) { - return display.getLayerStack() == layerStack; - }).get(); - } - } - - if (display == nullptr) { - display = renderArea->getDisplayDevice().get(); - } - - if (display == nullptr) { - display = getDefaultDisplayDeviceLocked().get(); - } + if (display != nullptr) { + captureResults.capturedDataspace = + pickBestDataspace(requestedDataspace, display.get(), + captureResults.capturedHdrLayers, + renderArea->getHintForSeamlessTransition()); + sdrWhitePointNits = state.sdrWhitePointNits; - if (display != nullptr) { - const auto& state = display->getCompositionDisplay()->getState(); - captureResults.capturedDataspace = - pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers, - renderArea->getHintForSeamlessTransition()); - sdrWhitePointNits = state.sdrWhitePointNits; - - if (!captureResults.capturedHdrLayers) { - displayBrightnessNits = sdrWhitePointNits; - } else { - displayBrightnessNits = state.displayBrightnessNits; - - if (!enableLocalTonemapping) { - // Only clamp the display brightness if this is not a seamless transition. - // Otherwise for seamless transitions it's important to match the current - // display state as the buffer will be shown under these same conditions, and we - // want to avoid any flickers - if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) { - // Restrict the amount of HDR "headroom" in the screenshot to avoid - // over-dimming the SDR portion. 2.0 chosen by experimentation - constexpr float kMaxScreenshotHeadroom = 2.0f; - displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, - displayBrightnessNits); - } + if (!captureResults.capturedHdrLayers) { + displayBrightnessNits = sdrWhitePointNits; + } else { + displayBrightnessNits = state.displayBrightnessNits; + if (!enableLocalTonemapping) { + // Only clamp the display brightness if this is not a seamless transition. + // Otherwise for seamless transitions it's important to match the current + // display state as the buffer will be shown under these same conditions, and we + // want to avoid any flickers + if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) { + // Restrict the amount of HDR "headroom" in the screenshot to avoid + // over-dimming the SDR portion. 2.0 chosen by experimentation + constexpr float kMaxScreenshotHeadroom = 2.0f; + displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom, + displayBrightnessNits); } } + } - // Screenshots leaving the device should be colorimetric - if (requestedDataspace == ui::Dataspace::UNKNOWN && - renderArea->getHintForSeamlessTransition()) { - renderIntent = state.renderIntent; - } + // Screenshots leaving the device should be colorimetric + if (requestedDataspace == ui::Dataspace::UNKNOWN && + renderArea->getHintForSeamlessTransition()) { + renderIntent = state.renderIntent; } } diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 8a390162d9..209d9bcfe6 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -898,25 +898,26 @@ private: ui::Size bufferSize, ui::PixelFormat, bool allowProtected, bool grayscale, const sp&); + using OutputCompositionState = compositionengine::impl::OutputCompositionState; + + const sp getRenderAreaDisplay(RenderAreaBuilderVariant& renderAreaBuilder, + OutputCompositionState& state) + REQUIRES(kMainThreadContext); + + std::vector>> getLayerSnapshotsFromMainThread( + GetLayerSnapshotsFunction getLayerSnapshotsFn) REQUIRES(kMainThreadContext); + ftl::SharedFuture captureScreenshot( RenderAreaBuilderVariant, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, const sp&); - // Overloaded version of renderScreenImpl that is used when layer snapshots have - // not yet been captured, and thus cannot yet be passed in as a parameter. - // Needed for TestableSurfaceFlinger. - ftl::SharedFuture renderScreenImpl( - std::unique_ptr, GetLayerSnapshotsFunction, - const std::shared_ptr&, bool regionSampling, - bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock) - REQUIRES(kMainThreadContext); - ftl::SharedFuture renderScreenImpl( std::unique_ptr, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&, - std::vector>>& layers) EXCLUDES(mStateLock) + const sp display, const OutputCompositionState& state, + std::vector>>& layers) REQUIRES(kMainThreadContext); // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 0ddddbd7f3..98f1687ac6 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -215,7 +215,7 @@ void CompositionTest::captureScreenComposition() { HAL_PIXEL_FORMAT_RGBA_8888, 1, usage); - auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots, + auto future = mFlinger.renderScreenImpl(mDisplay, std::move(renderArea), getLayerSnapshots, mCaptureScreenBuffer, regionSampling); ASSERT_TRUE(future.valid()); const auto fenceResult = future.get(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 265f804fc8..1783e179e2 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -485,16 +485,21 @@ public: return mFlinger->setPowerModeInternal(display, mode); } - auto renderScreenImpl(std::unique_ptr renderArea, + auto renderScreenImpl(const sp display, + std::unique_ptr renderArea, SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers, const std::shared_ptr& buffer, bool regionSampling) { + Mutex::Autolock lock(mFlinger->mStateLock); + ftl::FakeGuard guard(kMainThreadContext); + ScreenCaptureResults captureResults; - return FTL_FAKE_GUARD(kMainThreadContext, - mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers, - buffer, regionSampling, - false /* grayscale */, - false /* isProtected */, captureResults)); + SurfaceFlinger::OutputCompositionState state = display->getCompositionDisplay()->getState(); + auto layers = mFlinger->getLayerSnapshotsFromMainThread(traverseLayers); + + return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling, + false /* grayscale */, false /* isProtected */, + captureResults, display, state, layers); } auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid, -- GitLab From 6597c5405f164fe5602ae5aba4e0c4fc41c20389 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Mon, 3 Jun 2024 23:31:47 -0700 Subject: [PATCH 402/465] Clean up dangling layer hierarchy references In order to support detached mirrors, we introduced a different path for mirroring a hierarchy that excludes the root layer's transform. However, when the mirrored layer was destroyed, references to this layer were not properly removed from its mirroring hierarchy. Fix this and introduce a test to cover this scenario. Flag: EXEMPT bug fix Fixes: 343901186 Test: presumbit Change-Id: I32f41fc2c9db00590ede4509d75000ab0a38f116 --- .../FrontEnd/LayerLifecycleManager.cpp | 4 ++++ .../tests/unittests/LayerHierarchyTest.cpp | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp index 4b0618e5aa..dd5e8bd007 100644 --- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp +++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp @@ -152,6 +152,10 @@ void LayerLifecycleManager::onHandlesDestroyed( if (swapErase(linkedLayer->mirrorIds, layer.id)) { linkedLayer->changes |= RequestedLayerState::Changes::Mirror; } + if (linkedLayer->layerIdToMirror == layer.id) { + linkedLayer->layerIdToMirror = UNASSIGNED_LAYER_ID; + linkedLayer->changes |= RequestedLayerState::Changes::Mirror; + } if (linkedLayer->touchCropId == layer.id) { linkedLayer->touchCropId = UNASSIGNED_LAYER_ID; } diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp index 2b333f4b87..b79bdb4231 100644 --- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp @@ -777,4 +777,28 @@ TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) { EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected); } +// (b/343901186) +TEST_F(LayerHierarchyTest, cleanUpDanglingMirrorLayer) { + LayerHierarchyBuilder hierarchyBuilder; + hierarchyBuilder.update(mLifecycleManager); + mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 2); + UPDATE_AND_VERIFY(hierarchyBuilder); + + std::vector expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14, 2, 2}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); + + // destroy layer handle + reparentLayer(2, UNASSIGNED_LAYER_ID); + destroyLayerHandle(2); + UPDATE_AND_VERIFY(hierarchyBuilder); + expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath); + expectedTraversalPath = {}; + EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath); +} + } // namespace android::surfaceflinger::frontend -- GitLab From a32a1198c5a30f583600c7d4953a6e7fe13e5b41 Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Tue, 4 Jun 2024 15:10:31 +0000 Subject: [PATCH 403/465] InputMapper: pass RawEvent references instead of pointers We never pass null values here, so changing to references makes this clearer. Change-Id: I82d5359e244a93560bcbef477c89fef016168abb Test: atest inputflinger_test Test: m checkinput Test: manually test basic functionality on a multi-touch screen, touchpad, mouse, and drawing tablet Bug: 245989146 Flag: EXEMPT refactor --- services/inputflinger/reader/InputDevice.cpp | 2 +- .../reader/mapper/CursorInputMapper.cpp | 12 +++++----- .../reader/mapper/CursorInputMapper.h | 2 +- .../mapper/ExternalStylusInputMapper.cpp | 10 ++++----- .../reader/mapper/ExternalStylusInputMapper.h | 2 +- .../inputflinger/reader/mapper/InputMapper.h | 2 +- .../reader/mapper/JoystickInputMapper.cpp | 22 +++++++++---------- .../reader/mapper/JoystickInputMapper.h | 2 +- .../reader/mapper/KeyboardInputMapper.cpp | 10 ++++----- .../reader/mapper/KeyboardInputMapper.h | 2 +- .../reader/mapper/MultiTouchInputMapper.cpp | 4 ++-- .../reader/mapper/MultiTouchInputMapper.h | 2 +- .../mapper/RotaryEncoderInputMapper.cpp | 8 +++---- .../reader/mapper/RotaryEncoderInputMapper.h | 2 +- .../reader/mapper/SensorInputMapper.cpp | 16 +++++++------- .../reader/mapper/SensorInputMapper.h | 2 +- .../reader/mapper/SingleTouchInputMapper.cpp | 4 ++-- .../reader/mapper/SingleTouchInputMapper.h | 2 +- .../reader/mapper/SwitchInputMapper.cpp | 10 ++++----- .../reader/mapper/SwitchInputMapper.h | 2 +- .../reader/mapper/TouchInputMapper.cpp | 12 +++++----- .../reader/mapper/TouchInputMapper.h | 2 +- .../reader/mapper/TouchpadInputMapper.cpp | 10 ++++----- .../reader/mapper/TouchpadInputMapper.h | 2 +- .../reader/mapper/VibratorInputMapper.cpp | 2 +- .../reader/mapper/VibratorInputMapper.h | 2 +- .../inputflinger/tests/InputMapperTest.cpp | 4 ++-- .../inputflinger/tests/InputReader_test.cpp | 4 ++-- .../tests/fuzzers/CursorInputFuzzer.cpp | 2 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 2 +- .../tests/fuzzers/MultiTouchInputFuzzer.cpp | 2 +- .../tests/fuzzers/SwitchInputFuzzer.cpp | 2 +- .../tests/fuzzers/TouchpadInputFuzzer.cpp | 2 +- 33 files changed, 83 insertions(+), 83 deletions(-) diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index b807b2714f..ed05bddc74 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -403,7 +403,7 @@ std::list InputDevice::process(const RawEvent* rawEvents, size_t cou mDropUntilNextSync = true; } else { for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) { - out += mapper.process(rawEvent); + out += mapper.process(*rawEvent); }); } --count; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp index 90de74564c..20cdb59b00 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp @@ -215,16 +215,16 @@ std::list CursorInputMapper::reset(nsecs_t when) { return InputMapper::reset(when); } -std::list CursorInputMapper::process(const RawEvent* rawEvent) { +std::list CursorInputMapper::process(const RawEvent& rawEvent) { std::list out; - mCursorButtonAccumulator.process(*rawEvent); - mCursorMotionAccumulator.process(*rawEvent); - mCursorScrollAccumulator.process(*rawEvent); + mCursorButtonAccumulator.process(rawEvent); + mCursorMotionAccumulator.process(rawEvent); + mCursorScrollAccumulator.process(rawEvent); - if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { const auto [eventTime, readTime] = applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), - rawEvent->when, rawEvent->readTime, + rawEvent.when, rawEvent.readTime, mLastEventTime); out += sync(eventTime, readTime); mLastEventTime = eventTime; diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h index 5bde32b1c0..2108488936 100644 --- a/services/inputflinger/reader/mapper/CursorInputMapper.h +++ b/services/inputflinger/reader/mapper/CursorInputMapper.h @@ -62,7 +62,7 @@ public: const InputReaderConfiguration& readerConfig, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp index 7f7e0dd9e5..3af1d04073 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp @@ -61,13 +61,13 @@ std::list ExternalStylusInputMapper::reset(nsecs_t when) { return InputMapper::reset(when); } -std::list ExternalStylusInputMapper::process(const RawEvent* rawEvent) { +std::list ExternalStylusInputMapper::process(const RawEvent& rawEvent) { std::list out; - mSingleTouchMotionAccumulator.process(*rawEvent); - mTouchButtonAccumulator.process(*rawEvent); + mSingleTouchMotionAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); - if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out += sync(rawEvent->when); + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + out += sync(rawEvent.when); } return out; } diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h index 97df02b69f..c040a7b996 100644 --- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h +++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h @@ -39,7 +39,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; private: SingleTouchMotionAccumulator mSingleTouchMotionAccumulator; diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h index c7eea0e980..2c51448b4e 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -78,7 +78,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes); [[nodiscard]] virtual std::list reset(nsecs_t when); - [[nodiscard]] virtual std::list process(const RawEvent* rawEvent) = 0; + [[nodiscard]] virtual std::list process(const RawEvent& rawEvent) = 0; [[nodiscard]] virtual std::list timeoutExpired(nsecs_t when); virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp index 5ce4d30bb3..41e018d392 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp @@ -259,29 +259,29 @@ std::list JoystickInputMapper::reset(nsecs_t when) { return InputMapper::reset(when); } -std::list JoystickInputMapper::process(const RawEvent* rawEvent) { +std::list JoystickInputMapper::process(const RawEvent& rawEvent) { std::list out; - switch (rawEvent->type) { + switch (rawEvent.type) { case EV_ABS: { - auto it = mAxes.find(rawEvent->code); + auto it = mAxes.find(rawEvent.code); if (it != mAxes.end()) { Axis& axis = it->second; float newValue, highNewValue; switch (axis.axisInfo.mode) { case AxisInfo::MODE_INVERT: - newValue = (axis.rawAxisInfo.maxValue - rawEvent->value) * axis.scale + + newValue = (axis.rawAxisInfo.maxValue - rawEvent.value) * axis.scale + axis.offset; highNewValue = 0.0f; break; case AxisInfo::MODE_SPLIT: - if (rawEvent->value < axis.axisInfo.splitValue) { - newValue = (axis.axisInfo.splitValue - rawEvent->value) * axis.scale + + if (rawEvent.value < axis.axisInfo.splitValue) { + newValue = (axis.axisInfo.splitValue - rawEvent.value) * axis.scale + axis.offset; highNewValue = 0.0f; - } else if (rawEvent->value > axis.axisInfo.splitValue) { + } else if (rawEvent.value > axis.axisInfo.splitValue) { newValue = 0.0f; highNewValue = - (rawEvent->value - axis.axisInfo.splitValue) * axis.highScale + + (rawEvent.value - axis.axisInfo.splitValue) * axis.highScale + axis.highOffset; } else { newValue = 0.0f; @@ -289,7 +289,7 @@ std::list JoystickInputMapper::process(const RawEvent* rawEvent) { } break; default: - newValue = rawEvent->value * axis.scale + axis.offset; + newValue = rawEvent.value * axis.scale + axis.offset; highNewValue = 0.0f; break; } @@ -300,9 +300,9 @@ std::list JoystickInputMapper::process(const RawEvent* rawEvent) { } case EV_SYN: - switch (rawEvent->code) { + switch (rawEvent.code) { case SYN_REPORT: - out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false); + out += sync(rawEvent.when, rawEvent.readTime, /*force=*/false); break; } break; diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h index 313f0922b7..621d38bfaa 100644 --- a/services/inputflinger/reader/mapper/JoystickInputMapper.h +++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h @@ -35,7 +35,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; private: struct Axis { diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 21245555a2..1bc87a25a3 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -237,15 +237,15 @@ std::list KeyboardInputMapper::reset(nsecs_t when) { return out; } -std::list KeyboardInputMapper::process(const RawEvent* rawEvent) { +std::list KeyboardInputMapper::process(const RawEvent& rawEvent) { std::list out; - mHidUsageAccumulator.process(*rawEvent); - switch (rawEvent->type) { + mHidUsageAccumulator.process(rawEvent); + switch (rawEvent.type) { case EV_KEY: { - int32_t scanCode = rawEvent->code; + int32_t scanCode = rawEvent.code; if (isSupportedScanCode(scanCode)) { - out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, + out += processKey(rawEvent.when, rawEvent.readTime, rawEvent.value != 0, scanCode, mHidUsageAccumulator.consumeCurrentHidUsage()); } break; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index f2d3f4d7d8..74bef46d8d 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -36,7 +36,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp index 05ffa4d11f..1986fe286a 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp @@ -40,10 +40,10 @@ std::list MultiTouchInputMapper::reset(nsecs_t when) { return TouchInputMapper::reset(when); } -std::list MultiTouchInputMapper::process(const RawEvent* rawEvent) { +std::list MultiTouchInputMapper::process(const RawEvent& rawEvent) { std::list out = TouchInputMapper::process(rawEvent); - mMultiTouchMotionAccumulator.process(*rawEvent); + mMultiTouchMotionAccumulator.process(rawEvent); return out; } diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h index 5c173f365e..cca23279c5 100644 --- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h @@ -31,7 +31,7 @@ public: ~MultiTouchInputMapper() override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; [[nodiscard]] std::list reconfigure(nsecs_t when, const InputReaderConfiguration& config, ConfigurationChanges changes) override; diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp index 182e1b715e..27ff52fa65 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp @@ -101,12 +101,12 @@ std::list RotaryEncoderInputMapper::reset(nsecs_t when) { return InputMapper::reset(when); } -std::list RotaryEncoderInputMapper::process(const RawEvent* rawEvent) { +std::list RotaryEncoderInputMapper::process(const RawEvent& rawEvent) { std::list out; - mRotaryEncoderScrollAccumulator.process(*rawEvent); + mRotaryEncoderScrollAccumulator.process(rawEvent); - if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out += sync(rawEvent->when, rawEvent->readTime); + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + out += sync(rawEvent.when, rawEvent.readTime); } return out; } diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h index fe5d152cef..14c540bf6e 100644 --- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h +++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h @@ -39,7 +39,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; private: CursorScrollAccumulator mRotaryEncoderScrollAccumulator; diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp index a131e3598f..d7f2993daa 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp @@ -251,35 +251,35 @@ void SensorInputMapper::processHardWareTimestamp(nsecs_t evTime, int32_t mscTime mPrevMscTime = static_cast(mscTime); } -std::list SensorInputMapper::process(const RawEvent* rawEvent) { +std::list SensorInputMapper::process(const RawEvent& rawEvent) { std::list out; - switch (rawEvent->type) { + switch (rawEvent.type) { case EV_ABS: { - auto it = mAxes.find(rawEvent->code); + auto it = mAxes.find(rawEvent.code); if (it != mAxes.end()) { Axis& axis = it->second; - axis.newValue = rawEvent->value * axis.scale + axis.offset; + axis.newValue = rawEvent.value * axis.scale + axis.offset; } break; } case EV_SYN: - switch (rawEvent->code) { + switch (rawEvent.code) { case SYN_REPORT: for (std::pair& pair : mAxes) { Axis& axis = pair.second; axis.currentValue = axis.newValue; } - out += sync(rawEvent->when, /*force=*/false); + out += sync(rawEvent.when, /*force=*/false); break; } break; case EV_MSC: - switch (rawEvent->code) { + switch (rawEvent.code) { case MSC_TIMESTAMP: // hardware timestamp is nano seconds - processHardWareTimestamp(rawEvent->when, rawEvent->value); + processHardWareTimestamp(rawEvent.when, rawEvent.value); break; } } diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h index a55dcd1905..63bc151ac1 100644 --- a/services/inputflinger/reader/mapper/SensorInputMapper.h +++ b/services/inputflinger/reader/mapper/SensorInputMapper.h @@ -40,7 +40,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, std::chrono::microseconds maxBatchReportLatency) override; void disableSensor(InputDeviceSensorType sensorType) override; diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp index b7365d3d13..140bb0c4ed 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp @@ -30,10 +30,10 @@ std::list SingleTouchInputMapper::reset(nsecs_t when) { return TouchInputMapper::reset(when); } -std::list SingleTouchInputMapper::process(const RawEvent* rawEvent) { +std::list SingleTouchInputMapper::process(const RawEvent& rawEvent) { std::list out = TouchInputMapper::process(rawEvent); - mSingleTouchMotionAccumulator.process(*rawEvent); + mSingleTouchMotionAccumulator.process(rawEvent); return out; } diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h index 7726bfb159..bc38711c98 100644 --- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h +++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h @@ -31,7 +31,7 @@ public: ~SingleTouchInputMapper() override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; protected: void syncTouch(nsecs_t when, RawState* outState) override; diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp index 05338da146..f131fb73df 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp @@ -30,16 +30,16 @@ uint32_t SwitchInputMapper::getSources() const { return AINPUT_SOURCE_SWITCH; } -std::list SwitchInputMapper::process(const RawEvent* rawEvent) { +std::list SwitchInputMapper::process(const RawEvent& rawEvent) { std::list out; - switch (rawEvent->type) { + switch (rawEvent.type) { case EV_SW: - processSwitch(rawEvent->code, rawEvent->value); + processSwitch(rawEvent.code, rawEvent.value); break; case EV_SYN: - if (rawEvent->code == SYN_REPORT) { - out += sync(rawEvent->when); + if (rawEvent.code == SYN_REPORT) { + out += sync(rawEvent.when); } } return out; diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h index 2fb48bbf25..5d8aa2c784 100644 --- a/services/inputflinger/reader/mapper/SwitchInputMapper.h +++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h @@ -29,7 +29,7 @@ public: virtual ~SwitchInputMapper(); virtual uint32_t getSources() const override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) override; virtual void dump(std::string& dump) override; diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 1e9f28a19d..3a86f3650e 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -1415,14 +1415,14 @@ void TouchInputMapper::clearStylusDataPendingFlags() { mExternalStylusFusionTimeout = LLONG_MAX; } -std::list TouchInputMapper::process(const RawEvent* rawEvent) { - mCursorButtonAccumulator.process(*rawEvent); - mCursorScrollAccumulator.process(*rawEvent); - mTouchButtonAccumulator.process(*rawEvent); +std::list TouchInputMapper::process(const RawEvent& rawEvent) { + mCursorButtonAccumulator.process(rawEvent); + mCursorScrollAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); std::list out; - if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) { - out += sync(rawEvent->when, rawEvent->readTime); + if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) { + out += sync(rawEvent.when, rawEvent.readTime); } return out; } diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h index b24f2ff8da..30c58a59c5 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -174,7 +174,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override; int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override; diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp index a97d646e3a..24efae893e 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp @@ -417,17 +417,17 @@ void TouchpadInputMapper::resetGestureInterpreter(nsecs_t when) { mResettingInterpreter = false; } -std::list TouchpadInputMapper::process(const RawEvent* rawEvent) { +std::list TouchpadInputMapper::process(const RawEvent& rawEvent) { if (mPointerCaptured) { - return mCapturedEventConverter.process(*rawEvent); + return mCapturedEventConverter.process(rawEvent); } if (mMotionAccumulator.getActiveSlotsCount() == 0) { - mGestureStartTime = rawEvent->when; + mGestureStartTime = rawEvent.when; } - std::optional state = mStateConverter.processRawEvent(*rawEvent); + std::optional state = mStateConverter.processRawEvent(rawEvent); if (state) { updatePalmDetectionMetrics(); - return sendHardwareState(rawEvent->when, rawEvent->readTime, *state); + return sendHardwareState(rawEvent.when, rawEvent.readTime, *state); } else { return {}; } diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h index 546fa5bb9c..8baa63e8e0 100644 --- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h @@ -56,7 +56,7 @@ public: const InputReaderConfiguration& config, ConfigurationChanges changes) override; [[nodiscard]] std::list reset(nsecs_t when) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; [[nodiscard]] std::list timeoutExpired(nsecs_t when) override; void consumeGesture(const Gesture* gesture); diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp index 8d78d0fd80..a3a48ef034 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp @@ -36,7 +36,7 @@ void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo& info) { info.setVibrator(true); } -std::list VibratorInputMapper::process(const RawEvent* rawEvent) { +std::list VibratorInputMapper::process(const RawEvent& rawEvent) { // TODO: Handle FF_STATUS, although it does not seem to be widely supported. return {}; } diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h index 9079c73f8e..75196821af 100644 --- a/services/inputflinger/reader/mapper/VibratorInputMapper.h +++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h @@ -30,7 +30,7 @@ public: virtual uint32_t getSources() const override; virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override; - [[nodiscard]] std::list process(const RawEvent* rawEvent) override; + [[nodiscard]] std::list process(const RawEvent& rawEvent) override; [[nodiscard]] std::list vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) override; diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index fea1349b88..b5c9232a78 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -104,7 +104,7 @@ std::list InputMapperUnitTest::process(nsecs_t when, int32_t type, i event.type = type; event.code = code; event.value = value; - return mMapper->process(&event); + return mMapper->process(event); } const char* InputMapperTest::DEVICE_NAME = "device"; @@ -195,7 +195,7 @@ std::list InputMapperTest::process(InputMapper& mapper, nsecs_t when event.type = type; event.code = code; event.value = value; - std::list processArgList = mapper.process(&event); + std::list processArgList = mapper.process(event); for (const NotifyArgs& args : processArgList) { mFakeListener->notify(args); } diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 8536ff0676..04605ed878 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -309,9 +309,9 @@ private: return {}; } - std::list process(const RawEvent* rawEvent) override { + std::list process(const RawEvent& rawEvent) override { std::scoped_lock lock(mLock); - mLastEvent = *rawEvent; + mLastEvent = rawEvent; mProcessWasCalled = true; mStateChangedCondition.notify_all(); return mProcessResult; diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp index af20a271b8..836151787c 100644 --- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp @@ -81,7 +81,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { mapper.reconfigure(fdp->ConsumeIntegral(), policyConfig, InputReaderConfiguration::Change(0)); RawEvent rawEvent = getFuzzedRawEvent(*fdp); - unused += mapper.process(&rawEvent); + unused += mapper.process(rawEvent); }, [&]() -> void { std::list unused = mapper.reset(fdp->ConsumeIntegral()); diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 922cbdfb87..8c3189e089 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -80,7 +80,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { RawEvent rawEvent = getFuzzedRawEvent(*fdp); - std::list unused = mapper.process(&rawEvent); + std::list unused = mapper.process(rawEvent); }, [&]() -> void { mapper.getKeyCodeState(fdp->ConsumeIntegral(), diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp index d3f66900da..f29577d7f0 100644 --- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp @@ -100,7 +100,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { RawEvent rawEvent = getFuzzedRawEvent(*fdp); - std::list unused = mapper.process(&rawEvent); + std::list unused = mapper.process(rawEvent); }, [&]() -> void { mapper.getKeyCodeState(fdp->ConsumeIntegral(), diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp index ac2030afd3..a42d447fe3 100644 --- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp @@ -44,7 +44,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { [&]() -> void { mapper.getSources(); }, [&]() -> void { RawEvent rawEvent = getFuzzedRawEvent(*fdp); - std::list unused = mapper.process(&rawEvent); + std::list unused = mapper.process(rawEvent); }, [&]() -> void { mapper.getSwitchState(fdp->ConsumeIntegral(), diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp index 643e8b9f97..c620032eef 100644 --- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp @@ -176,7 +176,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { }, [&]() -> void { RawEvent event = getFuzzedRawEvent(*fdp); - std::list unused = mapper.process(&event); + std::list unused = mapper.process(event); }, })(); } -- GitLab From 683fe045ffbfd5de81ceb3385b73ad0d687f83a3 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Tue, 4 Jun 2024 17:22:04 +0000 Subject: [PATCH 404/465] Pass state instead of unsafe display on binder Accessing display from pickBestDataspace may be unsafe without mStateLock. The display was originally used to get the OutputCompositionState, which can instead be passed in directly. There is no need to check for a null display in this method anymore because the caller of this function already has the correct default value. Bug: b/294936197 Test: presubmit Change-Id: I10ce68a75f91d14fb18f457ac4eebb2ed54fe09a --- services/surfaceflinger/SurfaceFlinger.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d9f7804051..9777b4d4c9 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7846,14 +7846,13 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) { namespace { -ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayDevice* display, +ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, + const compositionengine::impl::OutputCompositionState& state, bool capturingHdrLayers, bool hintForSeamlessTransition) { - if (requestedDataspace != ui::Dataspace::UNKNOWN || display == nullptr) { + if (requestedDataspace != ui::Dataspace::UNKNOWN) { return requestedDataspace; } - const auto& state = display->getCompositionDisplay()->getState(); - const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode); // TODO: Enable once HDR screenshots are ready. @@ -8358,8 +8357,7 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( if (display != nullptr) { captureResults.capturedDataspace = - pickBestDataspace(requestedDataspace, display.get(), - captureResults.capturedHdrLayers, + pickBestDataspace(requestedDataspace, state, captureResults.capturedHdrLayers, renderArea->getHintForSeamlessTransition()); sdrWhitePointNits = state.sdrWhitePointNits; -- GitLab From eddfe939edcc9b3f4ce73cb6c2de21f588856df8 Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Tue, 4 Jun 2024 18:10:41 +0000 Subject: [PATCH 405/465] Remove legacy layer path in screenshot pathway Legacy path to get layer snapshots is no longer used and the flag can be removed to clean up the code. Also renames layer snapshot function variable for better clarity that the function is not immediately called wherever declared. Bug: b/330785038 Test: presubmit Test: atest SurfaceFlinger_test Change-Id: I12498f2560de07d69fff93eda901f6b7c072fce6 --- .../surfaceflinger/RegionSamplingThread.cpp | 44 +++------- services/surfaceflinger/SurfaceFlinger.cpp | 80 +++++-------------- .../tests/unittests/CompositionTest.cpp | 5 +- .../tests/unittests/TestableSurfaceFlinger.h | 7 +- 4 files changed, 35 insertions(+), 101 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 2b4e234604..59eb7f5d4a 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -315,39 +315,15 @@ void RegionSamplingThread::captureSample() { return true; }; - std::function>>()> getLayerSnapshots; - if (mFlinger.mLayerLifecycleManagerEnabled) { - auto filterFn = [&](const frontend::LayerSnapshot& snapshot, - bool& outStopTraversal) -> bool { - const Rect bounds = - frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds), - snapshot.transparentRegionHint); - const ui::Transform transform = snapshot.geomLayerTransform; - return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform, - outStopTraversal); - }; - getLayerSnapshots = - mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, - filterFn); - } else { - auto traverseLayers = [&](const LayerVector::Visitor& visitor) { - bool stopLayerFound = false; - auto filterVisitor = [&](Layer* layer) { - // We don't want to capture any layers beyond the stop layer - if (stopLayerFound) return; - - if (!layerFilterFn(layer->getDebugName(), layer->getSequence(), - Rect(layer->getBounds()), layer->getTransform(), - stopLayerFound)) { - return; - } - visitor(layer); - }; - mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, - filterVisitor); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - } + auto filterFn = [&](const frontend::LayerSnapshot& snapshot, bool& outStopTraversal) -> bool { + const Rect bounds = frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds), + snapshot.transparentRegionHint); + const ui::Transform transform = snapshot.geomLayerTransform; + return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform, + outStopTraversal); + }; + auto getLayerSnapshotsFn = + mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, filterFn); std::shared_ptr buffer = nullptr; if (mCachedBuffer && mCachedBuffer->getBuffer()->getWidth() == sampledBounds.getWidth() && @@ -379,7 +355,7 @@ void RegionSamplingThread::captureSample() { ui::Dataspace::V0_SRGB, kHintForSeamlessTransition, true /* captureSecureLayers */, displayWeak), - getLayerSnapshots, buffer, kRegionSampling, kGrayscale, + getLayerSnapshotsFn, buffer, kRegionSampling, kGrayscale, kIsProtected, nullptr) .get(); fenceResult.ok()) { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index d9f7804051..f0b390e1b5 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7933,23 +7933,14 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, } } - GetLayerSnapshotsFunction getLayerSnapshots; - if (mLayerLifecycleManagerEnabled) { - getLayerSnapshots = - getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds)); - } else { - auto traverseLayers = [this, args, excludeLayerIds, - layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, args.uid, std::move(excludeLayerIds), visitor); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - } + GetLayerSnapshotsFunction getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds)); captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, args.sourceCrop, reqSize, args.dataspace, args.hintForSeamlessTransition, args.captureSecureLayers, displayWeak), - getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, + getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); } @@ -7986,16 +7977,9 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args return; } - GetLayerSnapshotsFunction getLayerSnapshots; - if (mLayerLifecycleManagerEnabled) { - getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, - /*snapshotFilterFn=*/nullptr); - } else { - auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) { - traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, visitor); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); - } + GetLayerSnapshotsFunction getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, + /*snapshotFilterFn=*/nullptr); if (captureListener == nullptr) { ALOGE("capture screen must provide a capture listener callback"); @@ -8010,7 +7994,7 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args Rect(), size, args.dataspace, args.hintForSeamlessTransition, false /* captureSecureLayers */, displayWeak), - getLayerSnapshots, size, args.pixelFormat, kAllowProtected, kGrayscale, + getLayerSnapshotsFn, size, args.pixelFormat, kAllowProtected, kGrayscale, captureListener); } @@ -8092,42 +8076,16 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return; } - GetLayerSnapshotsFunction getLayerSnapshots; - if (mLayerLifecycleManagerEnabled) { - std::optional parentCrop = std::nullopt; - if (args.childrenOnly) { - parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) - : crop.toFloatRect(); - } - - getLayerSnapshots = getLayerSnapshotsForScreenshots(parent->sequence, args.uid, - std::move(excludeLayerIds), - args.childrenOnly, parentCrop); - } else { - auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) { - parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) { - if (!layer->isVisible()) { - return; - } else if (args.childrenOnly && layer == parent.get()) { - return; - } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) { - return; - } - - auto p = sp::fromExisting(layer); - while (p != nullptr) { - if (excludeLayerIds.count(p->sequence) != 0) { - return; - } - p = p->getParent(); - } - - visitor(layer); - }); - }; - getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + std::optional parentCrop = std::nullopt; + if (args.childrenOnly) { + parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height) + : crop.toFloatRect(); } + GetLayerSnapshotsFunction getLayerSnapshotsFn = + getLayerSnapshotsForScreenshots(parent->sequence, args.uid, std::move(excludeLayerIds), + args.childrenOnly, parentCrop); + if (captureListener == nullptr) { ALOGD("capture screen must provide a capture listener callback"); invokeScreenCaptureError(BAD_VALUE, captureListener); @@ -8138,7 +8096,7 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, reqSize, dataspace, args.captureSecureLayers, args.hintForSeamlessTransition, parent, args.childrenOnly), - getLayerSnapshots, reqSize, args.pixelFormat, args.allowProtected, + getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); } @@ -8167,7 +8125,7 @@ bool SurfaceFlinger::layersHasProtectedLayer( } void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder, - GetLayerSnapshotsFunction getLayerSnapshots, + GetLayerSnapshotsFunction getLayerSnapshotsFn, ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale, const sp& captureListener) { @@ -8188,7 +8146,7 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuil const bool supportsProtected = getRenderEngine().supportsProtectedContent(); bool hasProtectedLayer = false; if (allowProtected && supportsProtected) { - auto layers = mScheduler->schedule([=]() { return getLayerSnapshots(); }).get(); + auto layers = mScheduler->schedule([=]() { return getLayerSnapshotsFn(); }).get(); hasProtectedLayer = layersHasProtectedLayer(layers); } const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected; @@ -8215,7 +8173,7 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuil renderengine::impl::ExternalTexture::Usage:: WRITEABLE); auto futureFence = - captureScreenshot(renderAreaBuilder, getLayerSnapshots, texture, + captureScreenshot(renderAreaBuilder, getLayerSnapshotsFn, texture, false /* regionSampling */, grayscale, isProtected, captureListener); futureFence.get(); } diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 98f1687ac6..08973de195 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -205,7 +205,8 @@ void CompositionTest::captureScreenComposition() { CaptureArgs::UNSET_UID, {}, visitor); }; - auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers); + // TODO: Use SurfaceFlinger::getLayerSnapshotsForScreenshots instead of this legacy function + auto getLayerSnapshotsFn = RenderArea::fromTraverseLayersLambda(traverseLayers); const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE; @@ -215,7 +216,7 @@ void CompositionTest::captureScreenComposition() { HAL_PIXEL_FORMAT_RGBA_8888, 1, usage); - auto future = mFlinger.renderScreenImpl(mDisplay, std::move(renderArea), getLayerSnapshots, + auto future = mFlinger.renderScreenImpl(mDisplay, std::move(renderArea), getLayerSnapshotsFn, mCaptureScreenBuffer, regionSampling); ASSERT_TRUE(future.valid()); const auto fenceResult = future.get(); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 1783e179e2..7889096fe2 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -487,7 +487,7 @@ public: auto renderScreenImpl(const sp display, std::unique_ptr renderArea, - SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers, + SurfaceFlinger::GetLayerSnapshotsFunction getLayerSnapshotsFn, const std::shared_ptr& buffer, bool regionSampling) { Mutex::Autolock lock(mFlinger->mStateLock); @@ -495,7 +495,7 @@ public: ScreenCaptureResults captureResults; SurfaceFlinger::OutputCompositionState state = display->getCompositionDisplay()->getState(); - auto layers = mFlinger->getLayerSnapshotsFromMainThread(traverseLayers); + auto layers = mFlinger->getLayerSnapshotsFromMainThread(getLayerSnapshotsFn); return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling, false /* grayscale */, false /* isProtected */, @@ -505,8 +505,7 @@ public: auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid, std::unordered_set excludeLayerIds, const LayerVector::Visitor& visitor) { - return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid, - excludeLayerIds, visitor); + return mFlinger->traverseLayersInLayerStack(layerStack, uid, excludeLayerIds, visitor); } auto getDisplayNativePrimaries(const sp& displayToken, -- GitLab From eb75bc8f50a5833d60c7eb1092202fb40f61724e Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Tue, 4 Jun 2024 13:05:45 -0700 Subject: [PATCH 406/465] Verify all injected events Prior to this CL, we only verified the streams from events injected from accessibility. With this CL, we will now verify all injected events, even if they did not originate from a11y. That means, these events were injected from tests. For now, we will only log the fact that the stream is inconsistent, but still let it through. Eventually, we can fail the injection instead, which will prevent crashes in the dispatcher. Bug: 329439551 Test: atest inputflinger_tests Change-Id: Ie7313fd79f614d3347ef7198812be3c810eb86ea --- services/inputflinger/dispatcher/InputDispatcher.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 9b97629b2e..527edb6fe6 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -4885,10 +4885,11 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev mLock.lock(); - if (policyFlags & POLICY_FLAG_FILTERED) { - // The events from InputFilter impersonate real hardware devices. Check these - // events for consistency and print an error. An inconsistent event sent from - // InputFilter could cause a crash in the later stages of dispatching pipeline. + { + // Verify all injected streams, whether the injection is coming from apps or from + // input filter. Print an error if the stream becomes inconsistent with this event. + // An inconsistent injected event sent could cause a crash in the later stages of + // dispatching pipeline. auto [it, _] = mInputFilterVerifiersByDisplay.try_emplace(displayId, std::string("Injection on ") + -- GitLab From ed7137cb352db839183e35371232fbb12b4a25b9 Mon Sep 17 00:00:00 2001 From: Rocky Fang Date: Tue, 4 Jun 2024 21:59:32 +0000 Subject: [PATCH 407/465] Fix flag configuration Sensor event queue does not belong to sensor service but lib sensor. Bug: 333132224 Test: presubmit Change-Id: Icf93c9596e24b0c09c5af3010d9f5ea43b30d0c6 --- libs/sensor/libsensor_flags.aconfig | 7 +++++++ services/sensorservice/senserservice_flags.aconfig | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig index c511f4a72f..cbf3055fd4 100644 --- a/libs/sensor/libsensor_flags.aconfig +++ b/libs/sensor/libsensor_flags.aconfig @@ -8,3 +8,10 @@ flag { bug: "322228259" is_fixed_read_only: true } + +flag { + name: "sensor_event_queue_report_sensor_usage_in_tracing" + namespace: "sensors" + description: "When this flag is enabled, sensor event queue will report sensor usage when system trace is enabled." + bug: "333132224" +} \ No newline at end of file diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig index 4c1d314c7b..7abfbaab07 100644 --- a/services/sensorservice/senserservice_flags.aconfig +++ b/services/sensorservice/senserservice_flags.aconfig @@ -28,10 +28,3 @@ flag { description: "When this flag is enabled, sensor service will only erase dynamic sensor data at the end of the threadLoop to prevent race condition." bug: "329020894" } - -flag { - name: "sensor_service_report_sensor_usage_in_tracing" - namespace: "sensors" - description: "When this flag is enabled, sensor service will report sensor usage when system trace is enabled." - bug: "333132224" -} \ No newline at end of file -- GitLab From 0869c1334eb62e879a06bb690f7b4e0a5a5946cc Mon Sep 17 00:00:00 2001 From: Chris Forbes Date: Wed, 5 Jun 2024 13:49:11 +1200 Subject: [PATCH 408/465] Dispatch properly to either GPDIFP2 or KHR variant The KHR function pointer may not be populated. We can accept either. Bug: b/341758459 Change-Id: Idc7f2db78dff35d6120dab35bb5f25a7fa81ccb4 --- vulkan/libvulkan/swapchain.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp index 74d3d9dc64..9e67725b58 100644 --- a/vulkan/libvulkan/swapchain.cpp +++ b/vulkan/libvulkan/swapchain.cpp @@ -1174,7 +1174,8 @@ VkResult GetPhysicalDeviceSurfaceFormats2KHR( pSurfaceFormat); if (surfaceCompressionProps && - driver.GetPhysicalDeviceImageFormatProperties2KHR) { + (driver.GetPhysicalDeviceImageFormatProperties2KHR || + driver.GetPhysicalDeviceImageFormatProperties2)) { VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {}; imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2; @@ -1205,7 +1206,7 @@ VkResult GetPhysicalDeviceSurfaceFormats2KHR( imageFormatProps.pNext = &compressionProps; VkResult compressionRes = - driver.GetPhysicalDeviceImageFormatProperties2KHR( + GetPhysicalDeviceImageFormatProperties2( physicalDevice, &imageFormatInfo, &imageFormatProps); if (compressionRes == VK_SUCCESS) { -- GitLab From 1aaef320e3b32aa059581c1074ed74f8c8e5229d Mon Sep 17 00:00:00 2001 From: Harry Cutts Date: Wed, 5 Jun 2024 12:55:22 +0000 Subject: [PATCH 409/465] PropertyProvider: remove unnecessary string copy Change-Id: I38ffe0b5dd175baf037a11d5412e2f14eb24ec46 Test: atest inputflinger_test Bug: 245989146 Flag: EXEMPT refactor --- .../inputflinger/reader/mapper/gestures/PropertyProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp index 69264f84ed..f4a5e0dfeb 100644 --- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp +++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp @@ -90,7 +90,7 @@ void PropertyProvider::loadPropertiesFromIdcFile(const PropertyMap& idcPropertie // prefixed with "gestureProp." and have spaces replaced by underscores. So, for example, the // configuration key "gestureProp.Palm_Width" refers to the "Palm Width" property. const std::string gesturePropPrefix = "gestureProp."; - for (const std::string key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) { + for (const std::string& key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) { std::string propertyName = key.substr(gesturePropPrefix.length()); for (size_t i = 0; i < propertyName.length(); i++) { if (propertyName[i] == '_') { -- GitLab From fa20c7fbbedd33c31664dfa197597c3425813c82 Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Tue, 4 Jun 2024 15:35:03 +0000 Subject: [PATCH 410/465] switch over to use new stoarge read api Bug: b/321077378 Test: m and avd Change-Id: Idbc970b835e9fd2177128b763916a4a1c3d6d478 --- services/sensorservice/Android.bp | 1 + services/surfaceflinger/Android.bp | 1 + 2 files changed, 2 insertions(+) diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp index afaf0ae84f..f4b0265afb 100644 --- a/services/sensorservice/Android.bp +++ b/services/sensorservice/Android.bp @@ -84,6 +84,7 @@ cc_library { "android.hardware.common-V2-ndk", "android.hardware.common.fmq-V1-ndk", "server_configurable_flags", + "libaconfig_storage_read_api_cc", ], static_libs: [ diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp index 4455383367..1b6c598372 100644 --- a/services/surfaceflinger/Android.bp +++ b/services/surfaceflinger/Android.bp @@ -85,6 +85,7 @@ cc_defaults { "libui", "libutils", "libSurfaceFlingerProp", + "libaconfig_storage_read_api_cc" ], static_libs: [ "iinputflinger_aidl_lib_static", -- GitLab From a920922c38f1b1b8612f44e8aa32333be224e5e8 Mon Sep 17 00:00:00 2001 From: Henry Wang Date: Fri, 31 May 2024 16:22:58 +0800 Subject: [PATCH 411/465] Add addService to pass in dump priority flags. Bug: 342937835 Test: Service can be added with dump priority critical or high. Signed-off-by: Henry Wang (cherry picked from https://android-review.googlesource.com/q/commit:acb3a5a9b214fbb7c54e94afe77bf4fb0f0793eb) Change-Id: I59a3fa3662ae253b5d2ab89071603a6e6eb81349 --- .../include_platform/android/binder_manager.h | 6 +++++- libs/binder/ndk/service_manager.cpp | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h index 52edae4a38..41b30a0a0f 100644 --- a/libs/binder/ndk/include_platform/android/binder_manager.h +++ b/libs/binder/ndk/include_platform/android/binder_manager.h @@ -30,7 +30,11 @@ enum AServiceManager_AddServiceFlag : uint32_t { * Services with methods that perform file IO, web socket creation or ways to egress data must * not be added with this flag for privacy concerns. */ - ADD_SERVICE_ALLOW_ISOLATED = 1, + ADD_SERVICE_ALLOW_ISOLATED = 1 << 0, + ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL = 1 << 1, + ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH = 1 << 2, + ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL = 1 << 3, + ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT = 1 << 4, }; /** diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp index 5529455cc6..4436dbeed7 100644 --- a/libs/binder/ndk/service_manager.cpp +++ b/libs/binder/ndk/service_manager.cpp @@ -49,7 +49,25 @@ binder_exception_t AServiceManager_addServiceWithFlags(AIBinder* binder, const c sp sm = defaultServiceManager(); bool allowIsolated = flags & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED; - status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated); + int dumpFlags = 0; + if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL) { + dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL; + } + if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH) { + dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_HIGH; + } + if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL) { + dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_NORMAL; + } + if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT) { + dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT; + } + if (dumpFlags == 0) { + dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT; + } + status_t exception = + sm->addService(String16(instance), binder->getBinder(), allowIsolated, dumpFlags); + return PruneException(exception); } -- GitLab From 4a719e88f8050713cc75d655f3e75473e5404327 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Thu, 6 Jun 2024 12:12:09 -0700 Subject: [PATCH 412/465] SF: VsyncTimeline::isVSyncInPhase should use display rate VsyncTimeline::isVSyncInPhase accidentally use the render rate instead of the physical refresh rate to calculate the divisor. This will break frame rate override when both the compositor frame rate and an app frame rate are difrrent than the display refresh rate. Bug: 328352850 Test: presubmit Change-Id: If85e4689d28ea6bcd8057946cdcbc0fbc16dd1a3 --- .../Scheduler/VSyncPredictor.cpp | 4 +++- .../tests/unittests/VSyncPredictorTest.cpp | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index 85ce713703..dd3c4b074c 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -737,7 +737,9 @@ bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, F return ticks(TimePoint::fromNs(timePoint) - now); }; - Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns()); + Fps displayFps = !FlagManager::getInstance().vrr_bugfix_24q4() && mRenderRateOpt + ? *mRenderRateOpt + : Fps::fromPeriodNsecs(mIdealPeriod.ns()); const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate); const auto now = TimePoint::now(); diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp index eafba0ad8d..5109ea6793 100644 --- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp +++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp @@ -535,6 +535,28 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) { } } +TEST_F(VSyncPredictorTest, isVSyncInPhaseWithRenderRate) { + SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true); + auto last = mNow; + for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod)); + mNow += mPeriod; + last = mNow; + tracker.addVsyncTimestamp(mNow); + } + + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod)); + EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + mPeriod), Eq(mNow + 2 * mPeriod)); + + const auto renderRateFps = Fps::fromPeriodNsecs(mPeriod * 2); + tracker.setRenderRate(renderRateFps, /*applyImmediately*/ true); + + EXPECT_FALSE(tracker.isVSyncInPhase(mNow, renderRateFps)); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + mPeriod, renderRateFps)); + EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 2 * mPeriod, renderRateFps)); + EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, renderRateFps)); +} + TEST_F(VSyncPredictorTest, isVSyncInPhaseForDivisors) { auto last = mNow; for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) { -- GitLab From c8fdedb2c773d5b0774d535c96ac4f711e2e1a1f Mon Sep 17 00:00:00 2001 From: Josep del Rio Date: Tue, 21 May 2024 13:32:43 +0000 Subject: [PATCH 413/465] Prevent display brightness flicker on power button ag/22712141 tried to fix an issue where using system keys would not trigger user activity, allowing the device to go into sleep mode even while interacting with things like volume controls. The cause of this is that system shortcuts were never sent to keyguard, which is responsible for manging the power state. Unfortunately, the fix caused the side effect of considering the power button user activity, which would brighten the display if in dim mode before bringing the display to sleep. This was quite subtle as we only learned of this problem recently. This CL refines the logic introduced in ag/22712141, correcting the specific issue while restoring the original user activity logic. Bug: 312671872 Test: Flashed on device, tested on keyguard and several apps Flag: EXEMPT bugfix Change-Id: I57f98ec1b138d4e71cd8c21742eacd8a2813e9d3 --- .../dispatcher/InputDispatcher.cpp | 18 ++--- .../tests/FakeInputDispatcherPolicy.cpp | 7 ++ .../tests/FakeInputDispatcherPolicy.h | 3 + .../tests/InputDispatcher_test.cpp | 65 +++++++++---------- 4 files changed, 46 insertions(+), 47 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 9b97629b2e..ffdb1c419a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1894,8 +1894,6 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrinterceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE; @@ -1912,8 +1910,12 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptrreportDroppedKey(entry->id); - // Poke user activity for undispatched keys - pokeUserActivityLocked(*entry); + // Poke user activity for consumed keys, as it may have not been reported due to + // the focused window requesting user activity to be disabled + if (*dropReason == DropReason::POLICY && + mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) { + pokeUserActivityLocked(*entry); + } return true; } @@ -3313,22 +3315,16 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) { if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) { return; } - // If the key code is unknown, we don't consider it user activity - if (keyEntry.keyCode == AKEYCODE_UNKNOWN) { - return; - } // Don't inhibit events that were intercepted or are not passed to // the apps, like system shortcuts if (windowDisablingUserActivityInfo != nullptr && - keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP && - keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER) { + keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP) { if (DEBUG_DISPATCH_CYCLE) { ALOGD("Not poking user activity: disabled by window '%s'.", windowDisablingUserActivityInfo->name.c_str()); } return; } - break; } default: { diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp index 530416c7aa..e17ee3a5d9 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp @@ -215,6 +215,10 @@ void FakeInputDispatcherPolicy::setStaleEventTimeout(std::chrono::nanoseconds ti mStaleEventTimeout = timeout; } +void FakeInputDispatcherPolicy::setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching) { + mConsumeKeyBeforeDispatching = consumeKeyBeforeDispatching; +} + void FakeInputDispatcherPolicy::assertUserActivityNotPoked() { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); @@ -401,6 +405,9 @@ void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(ui::LogicalDisplay nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp&, const KeyEvent&, uint32_t) { + if (mConsumeKeyBeforeDispatching) { + return -1; + } nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count(); // Clear intercept state so we could dispatch the event in next wake. mInterceptKeyTimeout = 0ms; diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index 2c86146ba3..62ff10f8c8 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -115,6 +115,7 @@ public: void setUnhandledKeyHandler(std::function(const KeyEvent&)> handler); void assertUnhandledKeyReported(int32_t keycode); void assertUnhandledKeyNotReported(); + void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching); private: std::mutex mLock; @@ -144,6 +145,8 @@ private: std::chrono::nanoseconds mStaleEventTimeout = 1000ms; + bool mConsumeKeyBeforeDispatching = false; + BlockingQueue>> mNotifiedInteractions; std::condition_variable mNotifyUnhandledKey; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 8de28c680f..0ca84c2ded 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -610,28 +610,6 @@ static NotifyKeyArgs generateKeyArgs( return args; } -static NotifyKeyArgs generateSystemShortcutArgs( - int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { - nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); - // Define a valid key event. - NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, - AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C, - AMETA_META_ON, currentTime); - - return args; -} - -static NotifyKeyArgs generateAssistantKeyArgs( - int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) { - nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); - // Define a valid key event. - NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID, - AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST, - KEY_ASSISTANT, AMETA_NONE, currentTime); - - return args; -} - [[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, ui::LogicalDisplayId displayId, const std::vector& points) { @@ -6628,17 +6606,18 @@ TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) { window->consumeFocusEvent(true); - mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); + mDispatcher->notifyKey( + KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); // Window should receive key down event. window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT); - // Should have poked user activity + // Should have not poked user activity mDispatcher->waitForIdle(); mFakePolicy->assertUserActivityNotPoked(); } -TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { +TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceivePolicyConsumedKey) { std::shared_ptr application = std::make_shared(); sp window = sp::make(application, mDispatcher, "Fake Window", @@ -6650,31 +6629,36 @@ TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) { window->consumeFocusEvent(true); + mFakePolicy->setConsumeKeyBeforeDispatching(true); + mDispatcher->notifyKey( - generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); + KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); mDispatcher->waitForIdle(); - // System key is not passed down + // Key is not passed down window->assertNoEvents(); // Should have poked user activity mFakePolicy->assertUserActivityPoked(); } -TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { +TEST_F(InputDispatcherTest, FocusedWindow_PolicyConsumedKeyIgnoresDisableUserActivity) { std::shared_ptr application = std::make_shared(); sp window = sp::make(application, mDispatcher, "Fake Window", ui::LogicalDisplayId::DEFAULT); + window->setDisableUserActivity(true); window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); window->consumeFocusEvent(true); + mFakePolicy->setConsumeKeyBeforeDispatching(true); + mDispatcher->notifyKey( - generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); + KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build()); mDispatcher->waitForIdle(); // System key is not passed down @@ -6684,30 +6668,39 @@ TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) { mFakePolicy->assertUserActivityPoked(); } -TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) { +class DisableUserActivityInputDispatcherTest : public InputDispatcherTest, + public ::testing::WithParamInterface {}; + +TEST_P(DisableUserActivityInputDispatcherTest, NotPassedToUserUserActivity) { std::shared_ptr application = std::make_shared(); sp window = sp::make(application, mDispatcher, "Fake Window", ui::LogicalDisplayId::DEFAULT); - window->setDisableUserActivity(true); + window->setDisableUserActivity(GetParam()); + window->setFocusable(true); mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); setFocusedWindow(window); window->consumeFocusEvent(true); - mDispatcher->notifyKey( - generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT)); + mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .keyCode(AKEYCODE_A) + .policyFlags(0) + .build()); mDispatcher->waitForIdle(); - // System key is not passed down + // Key is not passed down window->assertNoEvents(); - // Should have poked user activity - mFakePolicy->assertUserActivityPoked(); + // Should not have poked user activity + mFakePolicy->assertUserActivityNotPoked(); } +INSTANTIATE_TEST_CASE_P(DisableUserActivity, DisableUserActivityInputDispatcherTest, + ::testing::Bool()); + TEST_F(InputDispatcherTest, InjectedTouchesPokeUserActivity) { std::shared_ptr application = std::make_shared(); sp window = -- GitLab From e58ffb9d38a73c5c0520a73aa2a985596d73bd9e Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Wed, 22 May 2024 17:38:25 +0000 Subject: [PATCH 414/465] Implement KeyboardClassifier interface in rust DD: go/project-imposter-android This CL includes: - Rust interface setup Next CL to include: - Basic categorization into alphabetic and non-alphabetic - Updating categorization based on key presses Test: atest --host libinput_rust_test Test: atest inputflinger_tests Bug: 263559234 Flag: com.android.input.flags.enable_keyboard_classifier_rust_impl Change-Id: I52773be992ddd8efaa9546e0af8b0a78515d931c --- include/android/input.h | 1 - include/input/Input.h | 10 ++ include/input/KeyboardClassifier.h | 53 ++++++ libs/input/Android.bp | 22 +++ libs/input/InputDevice.cpp | 5 +- libs/input/KeyboardClassifier.cpp | 89 ++++++++++ libs/input/android/os/IInputConstants.aidl | 153 ++++++++++++++++++ libs/input/input_flags.aconfig | 7 + libs/input/rust/input.rs | 141 +++++++++++++--- libs/input/rust/keyboard_classifier.rs | 62 +++++++ libs/input/rust/lib.rs | 101 +++++++++++- services/inputflinger/reader/InputDevice.cpp | 22 ++- services/inputflinger/reader/InputReader.cpp | 5 + .../inputflinger/reader/include/EventHub.h | 41 ++--- .../inputflinger/reader/include/InputDevice.h | 9 ++ .../inputflinger/reader/include/InputReader.h | 5 + .../reader/include/InputReaderContext.h | 3 + .../reader/mapper/KeyboardInputMapper.cpp | 24 ++- .../reader/mapper/KeyboardInputMapper.h | 4 +- .../inputflinger/tests/InputReader_test.cpp | 119 ++++++-------- services/inputflinger/tests/InterfaceMocks.h | 4 + .../tests/KeyboardInputMapper_test.cpp | 3 +- .../tests/fuzzers/KeyboardInputFuzzer.cpp | 9 +- .../tests/fuzzers/MapperHelpers.h | 2 + 24 files changed, 759 insertions(+), 135 deletions(-) create mode 100644 include/input/KeyboardClassifier.h create mode 100644 libs/input/KeyboardClassifier.cpp create mode 100644 libs/input/rust/keyboard_classifier.rs diff --git a/include/android/input.h b/include/android/input.h index fec56f02f9..ee98d7aee9 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -1002,7 +1002,6 @@ enum { * Keyboard types. * * Refer to the documentation on android.view.InputDevice for more details. - * Note: When adding a new keyboard type here InputDeviceInfo::setKeyboardType needs to be updated. */ enum { /** none */ diff --git a/include/input/Input.h b/include/input/Input.h index 3ca9c19876..dee3e7dcd0 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -258,6 +258,16 @@ enum class KeyState { ftl_last = VIRTUAL, }; +/** + * The keyboard type. This should have 1:1 correspondence with the values of anonymous enum + * defined in input.h + */ +enum class KeyboardType { + NONE = AINPUT_KEYBOARD_TYPE_NONE, + NON_ALPHABETIC = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, + ALPHABETIC = AINPUT_KEYBOARD_TYPE_ALPHABETIC, +}; + bool isStylusToolType(ToolType toolType); struct PointerProperties; diff --git a/include/input/KeyboardClassifier.h b/include/input/KeyboardClassifier.h new file mode 100644 index 0000000000..457d474ee7 --- /dev/null +++ b/include/input/KeyboardClassifier.h @@ -0,0 +1,53 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "rust/cxx.h" + +namespace android { + +namespace input { +namespace keyboardClassifier { +struct KeyboardClassifier; +} +} // namespace input + +/* + * Keyboard classifier to classify keyboard into alphabetic and non-alphabetic keyboards + */ +class KeyboardClassifier { +public: + KeyboardClassifier(); + /** + * Get the type of keyboard that the classifier currently believes the device to be. + */ + KeyboardType getKeyboardType(DeviceId deviceId); + void notifyKeyboardChanged(DeviceId deviceId, const InputDeviceIdentifier& identifier, + uint32_t deviceClasses); + void processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState); + +private: + std::optional> + mRustClassifier; + std::unordered_map mKeyboardTypeMap; +}; + +} // namespace android diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 8f44b3a0e1..4123ffcde1 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -117,6 +117,27 @@ rust_bindgen { "--allowlist-var=AINPUT_SOURCE_HDMI", "--allowlist-var=AINPUT_SOURCE_SENSOR", "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER", + "--allowlist-var=AINPUT_KEYBOARD_TYPE_NONE", + "--allowlist-var=AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC", + "--allowlist-var=AINPUT_KEYBOARD_TYPE_ALPHABETIC", + "--allowlist-var=AMETA_NONE", + "--allowlist-var=AMETA_ALT_ON", + "--allowlist-var=AMETA_ALT_LEFT_ON", + "--allowlist-var=AMETA_ALT_RIGHT_ON", + "--allowlist-var=AMETA_SHIFT_ON", + "--allowlist-var=AMETA_SHIFT_LEFT_ON", + "--allowlist-var=AMETA_SHIFT_RIGHT_ON", + "--allowlist-var=AMETA_SYM_ON", + "--allowlist-var=AMETA_FUNCTION_ON", + "--allowlist-var=AMETA_CTRL_ON", + "--allowlist-var=AMETA_CTRL_LEFT_ON", + "--allowlist-var=AMETA_CTRL_RIGHT_ON", + "--allowlist-var=AMETA_META_ON", + "--allowlist-var=AMETA_META_LEFT_ON", + "--allowlist-var=AMETA_META_RIGHT_ON", + "--allowlist-var=AMETA_CAPS_LOCK_ON", + "--allowlist-var=AMETA_NUM_LOCK_ON", + "--allowlist-var=AMETA_SCROLL_LOCK_ON", ], static_libs: [ @@ -208,6 +229,7 @@ cc_library { "InputVerifier.cpp", "Keyboard.cpp", "KeyCharacterMap.cpp", + "KeyboardClassifier.cpp", "KeyLayoutMap.cpp", "MotionPredictor.cpp", "MotionPredictorMetricsManager.cpp", diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp index bc678103c2..9333ab83a6 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -273,10 +273,7 @@ void InputDeviceInfo::addLightInfo(const InputDeviceLightInfo& info) { } void InputDeviceInfo::setKeyboardType(int32_t keyboardType) { - static_assert(AINPUT_KEYBOARD_TYPE_NONE < AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); - static_assert(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC < AINPUT_KEYBOARD_TYPE_ALPHABETIC); - // There can be multiple subdevices with different keyboard types, set it to the highest type - mKeyboardType = std::max(mKeyboardType, keyboardType); + mKeyboardType = keyboardType; } void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) { diff --git a/libs/input/KeyboardClassifier.cpp b/libs/input/KeyboardClassifier.cpp new file mode 100644 index 0000000000..0c2c7be582 --- /dev/null +++ b/libs/input/KeyboardClassifier.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2024 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. + */ + +#define LOG_TAG "KeyboardClassifier" + +#include +#include +#include +#include + +#include "input_cxx_bridge.rs.h" + +namespace input_flags = com::android::input::flags; + +using android::input::RustInputDeviceIdentifier; + +namespace android { + +KeyboardClassifier::KeyboardClassifier() { + if (input_flags::enable_keyboard_classifier()) { + mRustClassifier = android::input::keyboardClassifier::create(); + } +} + +KeyboardType KeyboardClassifier::getKeyboardType(DeviceId deviceId) { + if (mRustClassifier) { + return static_cast( + android::input::keyboardClassifier::getKeyboardType(**mRustClassifier, deviceId)); + } else { + auto it = mKeyboardTypeMap.find(deviceId); + if (it == mKeyboardTypeMap.end()) { + return KeyboardType::NONE; + } + return it->second; + } +} + +// Copied from EventHub.h +const uint32_t DEVICE_CLASS_KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD; +const uint32_t DEVICE_CLASS_ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY; + +void KeyboardClassifier::notifyKeyboardChanged(DeviceId deviceId, + const InputDeviceIdentifier& identifier, + uint32_t deviceClasses) { + if (mRustClassifier) { + RustInputDeviceIdentifier rustIdentifier; + rustIdentifier.name = identifier.name; + rustIdentifier.location = identifier.location; + rustIdentifier.unique_id = identifier.uniqueId; + rustIdentifier.bus = identifier.bus; + rustIdentifier.vendor = identifier.vendor; + rustIdentifier.product = identifier.product; + rustIdentifier.version = identifier.version; + rustIdentifier.descriptor = identifier.descriptor; + android::input::keyboardClassifier::notifyKeyboardChanged(**mRustClassifier, deviceId, + rustIdentifier, deviceClasses); + } else { + bool isKeyboard = (deviceClasses & DEVICE_CLASS_KEYBOARD) != 0; + bool hasAlphabeticKey = (deviceClasses & DEVICE_CLASS_ALPHAKEY) != 0; + mKeyboardTypeMap.insert_or_assign(deviceId, + isKeyboard ? (hasAlphabeticKey + ? KeyboardType::ALPHABETIC + : KeyboardType::NON_ALPHABETIC) + : KeyboardType::NONE); + } +} + +void KeyboardClassifier::processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState) { + if (mRustClassifier && + !android::input::keyboardClassifier::isFinalized(**mRustClassifier, deviceId)) { + android::input::keyboardClassifier::processKey(**mRustClassifier, deviceId, evdevCode, + metaState); + } +} + +} // namespace android diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl index 90ed2b7d06..e8746d000e 100644 --- a/libs/input/android/os/IInputConstants.aidl +++ b/libs/input/android/os/IInputConstants.aidl @@ -234,4 +234,157 @@ interface IInputConstants * time to adjust to changes in direction. */ const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9; + + + /* + * Input device class: Keyboard + * The input device is a keyboard or has buttons. + * + * @hide + */ + const int DEVICE_CLASS_KEYBOARD = 0x00000001; + + /* + * Input device class: Alphakey + * The input device is an alpha-numeric keyboard (not just a dial pad). + * + * @hide + */ + const int DEVICE_CLASS_ALPHAKEY = 0x00000002; + + /* + * Input device class: Touch + * The input device is a touchscreen or a touchpad (either single-touch or multi-touch). + * + * @hide + */ + const int DEVICE_CLASS_TOUCH = 0x00000004; + + /* + * Input device class: Cursor + * The input device is a cursor device such as a trackball or mouse. + * + * @hide + */ + const int DEVICE_CLASS_CURSOR = 0x00000008; + + /* + * Input device class: Multi-touch + * The input device is a multi-touch touchscreen or touchpad. + * + * @hide + */ + const int DEVICE_CLASS_TOUCH_MT = 0x00000010; + + /* + * Input device class: Dpad + * The input device is a directional pad (implies keyboard, has DPAD keys). + * + * @hide + */ + const int DEVICE_CLASS_DPAD = 0x00000020; + + /* + * Input device class: Gamepad + * The input device is a gamepad (implies keyboard, has BUTTON keys). + * + * @hide + */ + const int DEVICE_CLASS_GAMEPAD = 0x00000040; + + /* + * Input device class: Switch + * The input device has switches. + * + * @hide + */ + const int DEVICE_CLASS_SWITCH = 0x00000080; + + /* + * Input device class: Joystick + * The input device is a joystick (implies gamepad, has joystick absolute axes). + * + * @hide + */ + const int DEVICE_CLASS_JOYSTICK = 0x00000100; + + /* + * Input device class: Vibrator + * The input device has a vibrator (supports FF_RUMBLE). + * + * @hide + */ + const int DEVICE_CLASS_VIBRATOR = 0x00000200; + + /* + * Input device class: Mic + * The input device has a microphone. + * + * @hide + */ + const int DEVICE_CLASS_MIC = 0x00000400; + + /* + * Input device class: External Stylus + * The input device is an external stylus (has data we want to fuse with touch data). + * + * @hide + */ + const int DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800; + + /* + * Input device class: Rotary Encoder + * The input device has a rotary encoder. + * + * @hide + */ + const int DEVICE_CLASS_ROTARY_ENCODER = 0x00001000; + + /* + * Input device class: Sensor + * The input device has a sensor like accelerometer, gyro, etc. + * + * @hide + */ + const int DEVICE_CLASS_SENSOR = 0x00002000; + + /* + * Input device class: Battery + * The input device has a battery. + * + * @hide + */ + const int DEVICE_CLASS_BATTERY = 0x00004000; + + /* + * Input device class: Light + * The input device has sysfs controllable lights. + * + * @hide + */ + const int DEVICE_CLASS_LIGHT = 0x00008000; + + /* + * Input device class: Touchpad + * The input device is a touchpad, requiring an on-screen cursor. + * + * @hide + */ + const int DEVICE_CLASS_TOUCHPAD = 0x00010000; + + /* + * Input device class: Virtual + * The input device is virtual (not a real device, not part of UI configuration). + * + * @hide + */ + const int DEVICE_CLASS_VIRTUAL = 0x20000000; + + /* + * Input device class: External + * The input device is external (not built-in). + * + * @hide + */ + const int DEVICE_CLASS_EXTERNAL = 0x40000000; } diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig index 560166c2bd..a2192cbdc4 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -150,3 +150,10 @@ flag { description: "Hide touch and pointer indicators if a secure window is present on display" bug: "325252005" } + +flag { + name: "enable_keyboard_classifier" + namespace: "input" + description: "Keyboard classifier that classifies all keyboards into alphabetic or non-alphabetic" + bug: "263559234" +} diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index d0dbd6fa16..72b421cffe 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -16,22 +16,26 @@ //! Common definitions of the Android Input Framework in rust. +use crate::ffi::RustInputDeviceIdentifier; use bitflags::bitflags; -use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED; -use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; -use inputconstants::aidl::android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; -use inputconstants::aidl::android::os::IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; +use inputconstants::aidl::android::os::IInputConstants; use std::fmt; /// The InputDevice ID. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct DeviceId(pub i32); +/// The InputDevice equivalent for Rust inputflinger +#[derive(Debug)] +pub struct InputDevice { + /// InputDevice ID + pub device_id: DeviceId, + /// InputDevice unique identifier + pub identifier: RustInputDeviceIdentifier, + /// InputDevice classes (equivalent to EventHub InputDeviceClass) + pub classes: DeviceClass, +} + #[repr(u32)] pub enum SourceClass { None = input_bindgen::AINPUT_SOURCE_CLASS_NONE, @@ -192,23 +196,23 @@ bitflags! { #[derive(Debug)] pub struct MotionFlags: u32 { /// FLAG_WINDOW_IS_OBSCURED - const WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32; + const WINDOW_IS_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED as u32; /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED - const WINDOW_IS_PARTIALLY_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32; + const WINDOW_IS_PARTIALLY_OBSCURED = IInputConstants::MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED as u32; /// FLAG_HOVER_EXIT_PENDING - const HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32; + const HOVER_EXIT_PENDING = IInputConstants::MOTION_EVENT_FLAG_HOVER_EXIT_PENDING as u32; /// FLAG_IS_GENERATED_GESTURE - const IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32; + const IS_GENERATED_GESTURE = IInputConstants::MOTION_EVENT_FLAG_IS_GENERATED_GESTURE as u32; /// FLAG_CANCELED - const CANCELED = INPUT_EVENT_FLAG_CANCELED as u32; + const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32; /// FLAG_NO_FOCUS_CHANGE - const NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32; + const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32; /// FLAG_IS_ACCESSIBILITY_EVENT - const IS_ACCESSIBILITY_EVENT = INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32; + const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32; /// FLAG_TAINTED - const TAINTED = INPUT_EVENT_FLAG_TAINTED as u32; + const TAINTED = IInputConstants::INPUT_EVENT_FLAG_TAINTED as u32; /// FLAG_TARGET_ACCESSIBILITY_FOCUS - const TARGET_ACCESSIBILITY_FOCUS = MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32; + const TARGET_ACCESSIBILITY_FOCUS = IInputConstants::MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS as u32; } } @@ -220,6 +224,107 @@ impl Source { } } +bitflags! { + /// Device class of the input device. These are duplicated from Eventhub.h + /// We need to make sure the two version remain in sync when adding new classes. + #[derive(Debug, PartialEq)] + pub struct DeviceClass: u32 { + /// The input device is a keyboard or has buttons + const Keyboard = IInputConstants::DEVICE_CLASS_KEYBOARD as u32; + /// The input device is an alpha-numeric keyboard (not just a dial pad) + const AlphabeticKey = IInputConstants::DEVICE_CLASS_ALPHAKEY as u32; + /// The input device is a touchscreen or a touchpad (either single-touch or multi-touch) + const Touch = IInputConstants::DEVICE_CLASS_TOUCH as u32; + /// The input device is a cursor device such as a trackball or mouse. + const Cursor = IInputConstants::DEVICE_CLASS_CURSOR as u32; + /// The input device is a multi-touch touchscreen or touchpad. + const MultiTouch = IInputConstants::DEVICE_CLASS_TOUCH_MT as u32; + /// The input device is a directional pad (implies keyboard, has DPAD keys). + const Dpad = IInputConstants::DEVICE_CLASS_DPAD as u32; + /// The input device is a gamepad (implies keyboard, has BUTTON keys). + const Gamepad = IInputConstants::DEVICE_CLASS_GAMEPAD as u32; + /// The input device has switches. + const Switch = IInputConstants::DEVICE_CLASS_SWITCH as u32; + /// The input device is a joystick (implies gamepad, has joystick absolute axes). + const Joystick = IInputConstants::DEVICE_CLASS_JOYSTICK as u32; + /// The input device has a vibrator (supports FF_RUMBLE). + const Vibrator = IInputConstants::DEVICE_CLASS_VIBRATOR as u32; + /// The input device has a microphone. + const Mic = IInputConstants::DEVICE_CLASS_MIC as u32; + /// The input device is an external stylus (has data we want to fuse with touch data). + const ExternalStylus = IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS as u32; + /// The input device has a rotary encoder + const RotaryEncoder = IInputConstants::DEVICE_CLASS_ROTARY_ENCODER as u32; + /// The input device has a sensor like accelerometer, gyro, etc + const Sensor = IInputConstants::DEVICE_CLASS_SENSOR as u32; + /// The input device has a battery + const Battery = IInputConstants::DEVICE_CLASS_BATTERY as u32; + /// The input device has sysfs controllable lights + const Light = IInputConstants::DEVICE_CLASS_LIGHT as u32; + /// The input device is a touchpad, requiring an on-screen cursor. + const Touchpad = IInputConstants::DEVICE_CLASS_TOUCHPAD as u32; + /// The input device is virtual (not a real device, not part of UI configuration). + const Virtual = IInputConstants::DEVICE_CLASS_VIRTUAL as u32; + /// The input device is external (not built-in). + const External = IInputConstants::DEVICE_CLASS_EXTERNAL as u32; + } +} + +bitflags! { + /// Modifier state flags + #[derive(Debug, PartialEq)] + pub struct ModifierState: u32 { + /// No meta keys are pressed + const None = input_bindgen::AMETA_NONE; + /// This mask is used to check whether one of the ALT meta keys is pressed + const AltOn = input_bindgen::AMETA_ALT_ON; + /// This mask is used to check whether the left ALT meta key is pressed + const AltLeftOn = input_bindgen::AMETA_ALT_LEFT_ON; + /// This mask is used to check whether the right ALT meta key is pressed + const AltRightOn = input_bindgen::AMETA_ALT_RIGHT_ON; + /// This mask is used to check whether one of the SHIFT meta keys is pressed + const ShiftOn = input_bindgen::AMETA_SHIFT_ON; + /// This mask is used to check whether the left SHIFT meta key is pressed + const ShiftLeftOn = input_bindgen::AMETA_SHIFT_LEFT_ON; + /// This mask is used to check whether the right SHIFT meta key is pressed + const ShiftRightOn = input_bindgen::AMETA_SHIFT_RIGHT_ON; + /// This mask is used to check whether the SYM meta key is pressed + const SymOn = input_bindgen::AMETA_SYM_ON; + /// This mask is used to check whether the FUNCTION meta key is pressed + const FunctionOn = input_bindgen::AMETA_FUNCTION_ON; + /// This mask is used to check whether one of the CTRL meta keys is pressed + const CtrlOn = input_bindgen::AMETA_CTRL_ON; + /// This mask is used to check whether the left CTRL meta key is pressed + const CtrlLeftOn = input_bindgen::AMETA_CTRL_LEFT_ON; + /// This mask is used to check whether the right CTRL meta key is pressed + const CtrlRightOn = input_bindgen::AMETA_CTRL_RIGHT_ON; + /// This mask is used to check whether one of the META meta keys is pressed + const MetaOn = input_bindgen::AMETA_META_ON; + /// This mask is used to check whether the left META meta key is pressed + const MetaLeftOn = input_bindgen::AMETA_META_LEFT_ON; + /// This mask is used to check whether the right META meta key is pressed + const MetaRightOn = input_bindgen::AMETA_META_RIGHT_ON; + /// This mask is used to check whether the CAPS LOCK meta key is on + const CapsLockOn = input_bindgen::AMETA_CAPS_LOCK_ON; + /// This mask is used to check whether the NUM LOCK meta key is on + const NumLockOn = input_bindgen::AMETA_NUM_LOCK_ON; + /// This mask is used to check whether the SCROLL LOCK meta key is on + const ScrollLockOn = input_bindgen::AMETA_SCROLL_LOCK_ON; + } +} + +/// A rust enum representation of a Keyboard type. +#[repr(u32)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum KeyboardType { + /// KEYBOARD_TYPE_NONE + None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE, + /// KEYBOARD_TYPE_NON_ALPHABETIC + NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, + /// KEYBOARD_TYPE_ALPHABETIC + Alphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_ALPHABETIC, +} + #[cfg(test)] mod tests { use crate::input::SourceClass; diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs new file mode 100644 index 0000000000..bd3a51cb51 --- /dev/null +++ b/libs/input/rust/keyboard_classifier.rs @@ -0,0 +1,62 @@ +/* + * Copyright 2024 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. + */ + +//! Contains the KeyboardClassifier, that tries to identify whether an Input device is an +//! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device +//! in order to verify/change the inferred keyboard type. + +use crate::input::{DeviceId, InputDevice, KeyboardType}; +use crate::ModifierState; + +/// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic +/// keyboard or non-alphabetic keyboard +#[derive(Default)] +pub struct KeyboardClassifier {} + +impl KeyboardClassifier { + /// Create a new KeyboardClassifier + pub fn new() -> Self { + Default::default() + } + + /// Adds keyboard to KeyboardClassifier + pub fn notify_keyboard_changed(&mut self, _device: InputDevice) { + // TODO(b/263559234): Implement method + } + + /// Get keyboard type for a tracked keyboard in KeyboardClassifier + pub fn get_keyboard_type(&self, _device_id: DeviceId) -> KeyboardType { + // TODO(b/263559234): Implement method + KeyboardType::None + } + + /// Tells if keyboard type classification is finalized. Once finalized the classification can't + /// change until device is reconnected again. + pub fn is_finalized(&self, _device_id: DeviceId) -> bool { + // TODO(b/263559234): Implement method + false + } + + /// Process a key event and change keyboard type if required. + pub fn process_key( + &mut self, + _device_id: DeviceId, + _evdev_code: i32, + _modifier_state: ModifierState, + ) { + // TODO(b/263559234): Implement method + } +} diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs index fb3f520c01..5010475334 100644 --- a/libs/input/rust/lib.rs +++ b/libs/input/rust/lib.rs @@ -18,9 +18,13 @@ mod input; mod input_verifier; +mod keyboard_classifier; -pub use input::{DeviceId, MotionAction, MotionFlags, Source}; +pub use input::{ + DeviceClass, DeviceId, InputDevice, ModifierState, MotionAction, MotionFlags, Source, +}; pub use input_verifier::InputVerifier; +pub use keyboard_classifier::KeyboardClassifier; #[cxx::bridge(namespace = "android::input")] #[allow(unsafe_op_in_unsafe_fn)] @@ -47,7 +51,8 @@ mod ffi { /// } /// ``` type InputVerifier; - fn create(name: String) -> Box; + #[cxx_name = create] + fn create_input_verifier(name: String) -> Box; fn process_movement( verifier: &mut InputVerifier, device_id: i32, @@ -59,15 +64,53 @@ mod ffi { fn reset_device(verifier: &mut InputVerifier, device_id: i32); } + #[namespace = "android::input::keyboardClassifier"] + extern "Rust" { + /// Used to classify a keyboard into alphabetic and non-alphabetic + type KeyboardClassifier; + #[cxx_name = create] + fn create_keyboard_classifier() -> Box; + #[cxx_name = notifyKeyboardChanged] + fn notify_keyboard_changed( + classifier: &mut KeyboardClassifier, + device_id: i32, + identifier: RustInputDeviceIdentifier, + device_classes: u32, + ); + #[cxx_name = getKeyboardType] + fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32; + #[cxx_name = isFinalized] + fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool; + #[cxx_name = processKey] + fn process_key( + classifier: &mut KeyboardClassifier, + device_id: i32, + evdev_code: i32, + modifier_state: u32, + ); + } + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct RustPointerProperties { pub id: i32, } + + #[derive(Debug)] + pub struct RustInputDeviceIdentifier { + pub name: String, + pub location: String, + pub unique_id: String, + pub bus: u16, + pub vendor: u16, + pub product: u16, + pub version: u16, + pub descriptor: String, + } } -use crate::ffi::RustPointerProperties; +use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties}; -fn create(name: String) -> Box { +fn create_input_verifier(name: String) -> Box { Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents"))) } @@ -103,3 +146,53 @@ fn process_movement( fn reset_device(verifier: &mut InputVerifier, device_id: i32) { verifier.reset_device(DeviceId(device_id)); } + +fn create_keyboard_classifier() -> Box { + Box::new(KeyboardClassifier::new()) +} + +fn notify_keyboard_changed( + classifier: &mut KeyboardClassifier, + device_id: i32, + identifier: RustInputDeviceIdentifier, + device_classes: u32, +) { + let classes = DeviceClass::from_bits(device_classes); + if classes.is_none() { + panic!( + "The conversion of device class 0x{:08x} failed, please check if some device classes + have not been added to DeviceClass.", + device_classes + ); + } + classifier.notify_keyboard_changed(InputDevice { + device_id: DeviceId(device_id), + identifier, + classes: classes.unwrap(), + }); +} + +fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32 { + classifier.get_keyboard_type(DeviceId(device_id)) as u32 +} + +fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool { + classifier.is_finalized(DeviceId(device_id)) +} + +fn process_key( + classifier: &mut KeyboardClassifier, + device_id: i32, + evdev_code: i32, + meta_state: u32, +) { + let modifier_state = ModifierState::from_bits(meta_state); + if modifier_state.is_none() { + panic!( + "The conversion of meta state 0x{:08x} failed, please check if some meta state + have not been added to ModifierState.", + meta_state + ); + } + classifier.process_key(DeviceId(device_id), evdev_code, modifier_state.unwrap()); +} diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp index b807b2714f..2b33403737 100644 --- a/services/inputflinger/reader/InputDevice.cpp +++ b/services/inputflinger/reader/InputDevice.cpp @@ -237,6 +237,12 @@ std::list InputDevice::configureInternal(nsecs_t when, mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL); mHasMic = mClasses.test(InputDeviceClass::MIC); + // Update keyboard type + if (mClasses.test(InputDeviceClass::KEYBOARD)) { + mContext->getKeyboardClassifier().notifyKeyboardChanged(mId, mIdentifier, mClasses.get()); + mKeyboardType = mContext->getKeyboardClassifier().getKeyboardType(mId); + } + using Change = InputReaderConfiguration::Change; if (!changes.any() || !isIgnored()) { @@ -445,6 +451,7 @@ InputDeviceInfo InputDevice::getDeviceInfo() { mHasMic, getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID), {mShouldSmoothScroll}, isEnabled()); + outDeviceInfo.setKeyboardType(static_cast(mKeyboardType)); for_each_mapper( [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); }); @@ -517,13 +524,9 @@ std::vector> InputDevice::createMappers( // Keyboard-like devices. uint32_t keyboardSource = 0; - int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC; if (classes.test(InputDeviceClass::KEYBOARD)) { keyboardSource |= AINPUT_SOURCE_KEYBOARD; } - if (classes.test(InputDeviceClass::ALPHAKEY)) { - keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC; - } if (classes.test(InputDeviceClass::DPAD)) { keyboardSource |= AINPUT_SOURCE_DPAD; } @@ -532,8 +535,8 @@ std::vector> InputDevice::createMappers( } if (keyboardSource != 0) { - mappers.push_back(createInputMapper(contextPtr, readerConfig, - keyboardSource, keyboardType)); + mappers.push_back( + createInputMapper(contextPtr, readerConfig, keyboardSource)); } // Cursor-like devices. @@ -730,6 +733,13 @@ std::optional InputDevice::getBatteryEventHubId() const { return mController ? std::make_optional(mController->getEventHubId()) : std::nullopt; } +void InputDevice::setKeyboardType(KeyboardType keyboardType) { + if (mKeyboardType != keyboardType) { + mKeyboardType = keyboardType; + bumpGeneration(); + } +} + InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId) : mDevice(device), mContext(device.getContext()), diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp index b9523ef332..ab13ad489b 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -102,6 +102,7 @@ InputReader::InputReader(std::shared_ptr eventHub, mEventHub(eventHub), mPolicy(policy), mNextListener(listener), + mKeyboardClassifier(std::make_unique()), mGlobalMetaState(AMETA_NONE), mLedMetaState(AMETA_NONE), mGeneration(1), @@ -1076,4 +1077,8 @@ int32_t InputReader::ContextImpl::getNextId() { return mIdGenerator.nextId(); } +KeyboardClassifier& InputReader::ContextImpl::getKeyboardClassifier() { + return *mReader->mKeyboardClassifier; +} + } // namespace android diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h index 39d2f5baab..7cf584df78 100644 --- a/services/inputflinger/reader/include/EventHub.h +++ b/services/inputflinger/reader/include/EventHub.h @@ -85,64 +85,67 @@ std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info); /* * Input device classes. + * + * These classes are duplicated in rust side here: /frameworks/native/libs/input/rust/input.rs. + * If any new classes are added, we need to add them in rust input side too. */ enum class InputDeviceClass : uint32_t { /* The input device is a keyboard or has buttons. */ - KEYBOARD = 0x00000001, + KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD, /* The input device is an alpha-numeric keyboard (not just a dial pad). */ - ALPHAKEY = 0x00000002, + ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY, /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */ - TOUCH = 0x00000004, + TOUCH = android::os::IInputConstants::DEVICE_CLASS_TOUCH, /* The input device is a cursor device such as a trackball or mouse. */ - CURSOR = 0x00000008, + CURSOR = android::os::IInputConstants::DEVICE_CLASS_CURSOR, /* The input device is a multi-touch touchscreen or touchpad. */ - TOUCH_MT = 0x00000010, + TOUCH_MT = android::os::IInputConstants::DEVICE_CLASS_TOUCH_MT, /* The input device is a directional pad (implies keyboard, has DPAD keys). */ - DPAD = 0x00000020, + DPAD = android::os::IInputConstants::DEVICE_CLASS_DPAD, /* The input device is a gamepad (implies keyboard, has BUTTON keys). */ - GAMEPAD = 0x00000040, + GAMEPAD = android::os::IInputConstants::DEVICE_CLASS_GAMEPAD, /* The input device has switches. */ - SWITCH = 0x00000080, + SWITCH = android::os::IInputConstants::DEVICE_CLASS_SWITCH, /* The input device is a joystick (implies gamepad, has joystick absolute axes). */ - JOYSTICK = 0x00000100, + JOYSTICK = android::os::IInputConstants::DEVICE_CLASS_JOYSTICK, /* The input device has a vibrator (supports FF_RUMBLE). */ - VIBRATOR = 0x00000200, + VIBRATOR = android::os::IInputConstants::DEVICE_CLASS_VIBRATOR, /* The input device has a microphone. */ - MIC = 0x00000400, + MIC = android::os::IInputConstants::DEVICE_CLASS_MIC, /* The input device is an external stylus (has data we want to fuse with touch data). */ - EXTERNAL_STYLUS = 0x00000800, + EXTERNAL_STYLUS = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS, /* The input device has a rotary encoder */ - ROTARY_ENCODER = 0x00001000, + ROTARY_ENCODER = android::os::IInputConstants::DEVICE_CLASS_ROTARY_ENCODER, /* The input device has a sensor like accelerometer, gyro, etc */ - SENSOR = 0x00002000, + SENSOR = android::os::IInputConstants::DEVICE_CLASS_SENSOR, /* The input device has a battery */ - BATTERY = 0x00004000, + BATTERY = android::os::IInputConstants::DEVICE_CLASS_BATTERY, /* The input device has sysfs controllable lights */ - LIGHT = 0x00008000, + LIGHT = android::os::IInputConstants::DEVICE_CLASS_LIGHT, /* The input device is a touchpad, requiring an on-screen cursor. */ - TOUCHPAD = 0x00010000, + TOUCHPAD = android::os::IInputConstants::DEVICE_CLASS_TOUCHPAD, /* The input device is virtual (not a real device, not part of UI configuration). */ - VIRTUAL = 0x40000000, + VIRTUAL = android::os::IInputConstants::DEVICE_CLASS_VIRTUAL, /* The input device is external (not built-in). */ - EXTERNAL = 0x80000000, + EXTERNAL = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL, }; enum class SysfsClass : uint32_t { diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 4c9af2e30e..2a7e262bf5 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -79,6 +79,8 @@ public: inline bool isIgnored() { return !getMapperCount() && !mController; } + inline KeyboardType getKeyboardType() const { return mKeyboardType; } + bool isEnabled(); void dump(std::string& dump, const std::string& eventHubDevStr); @@ -124,6 +126,8 @@ public: void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode); + void setKeyboardType(KeyboardType keyboardType); + void bumpGeneration(); [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when); @@ -196,6 +200,7 @@ private: uint32_t mSources; bool mIsWaking; bool mIsExternal; + KeyboardType mKeyboardType = KeyboardType::NONE; std::optional mAssociatedDisplayPort; std::optional mAssociatedDisplayUniqueIdByPort; std::optional mAssociatedDisplayUniqueIdByDescriptor; @@ -470,6 +475,10 @@ public: } inline void bumpGeneration() { mDevice.bumpGeneration(); } inline const PropertyMap& getConfiguration() const { return mDevice.getConfiguration(); } + inline KeyboardType getKeyboardType() const { return mDevice.getKeyboardType(); } + inline void setKeyboardType(KeyboardType keyboardType) { + return mDevice.setKeyboardType(keyboardType); + } private: InputDevice& mDevice; diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h index 7e701c5341..6f8c289093 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -157,6 +157,7 @@ protected: void setLastKeyDownTimestamp(nsecs_t when) REQUIRES(mReader->mLock) REQUIRES(mLock) override; nsecs_t getLastKeyDownTimestamp() REQUIRES(mReader->mLock) REQUIRES(mLock) override; + KeyboardClassifier& getKeyboardClassifier() override; } mContext; friend class ContextImpl; @@ -176,6 +177,10 @@ private: // The next stage that should receive the events generated inside InputReader. InputListenerInterface& mNextListener; + + // Classifier for keyboard/keyboard-like devices + std::unique_ptr mKeyboardClassifier; + // As various events are generated inside InputReader, they are stored inside this list. The // list can only be accessed with the lock, so the events inside it are well-ordered. // Once the reader is done working, these events will be swapped into a temporary storage and diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h index 907a49faa2..e0e0ac2051 100644 --- a/services/inputflinger/reader/include/InputReaderContext.h +++ b/services/inputflinger/reader/include/InputReaderContext.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include "NotifyArgs.h" #include @@ -64,6 +65,8 @@ public: virtual void setLastKeyDownTimestamp(nsecs_t when) = 0; virtual nsecs_t getLastKeyDownTimestamp() = 0; + + virtual KeyboardClassifier& getKeyboardClassifier() = 0; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 21245555a2..2e32380662 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -21,6 +21,7 @@ #include "KeyboardInputMapper.h" #include +#include #include namespace android { @@ -96,8 +97,8 @@ static bool isMediaKey(int32_t keyCode) { KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, - uint32_t source, int32_t keyboardType) - : InputMapper(deviceContext, readerConfig), mSource(source), mKeyboardType(keyboardType) {} + uint32_t source) + : InputMapper(deviceContext, readerConfig), mSource(source) {} uint32_t KeyboardInputMapper::getSources() const { return mSource; @@ -131,7 +132,6 @@ std::optional KeyboardInputMapper::getKeyboardLayoutInfo() c void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) { InputMapper::populateDeviceInfo(info); - info.setKeyboardType(mKeyboardType); info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap()); std::optional keyboardLayoutInfo = getKeyboardLayoutInfo(); @@ -143,7 +143,6 @@ void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) { void KeyboardInputMapper::dump(std::string& dump) { dump += INDENT2 "Keyboard Input Mapper:\n"; dumpParameters(dump); - dump += StringPrintf(INDENT3 "KeyboardType: %d\n", mKeyboardType); dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(getOrientation()).c_str()); dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size()); dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState); @@ -327,13 +326,24 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read keyMetaState = mMetaState; } + DeviceId deviceId = getDeviceId(); + + // On first down: Process key for keyboard classification (will send reconfiguration if the + // keyboard type change) + if (down && !keyDownIndex) { + KeyboardClassifier& classifier = getDeviceContext().getContext()->getKeyboardClassifier(); + classifier.processKey(deviceId, scanCode, keyMetaState); + getDeviceContext().setKeyboardType(classifier.getKeyboardType(deviceId)); + } + + KeyboardType keyboardType = getDeviceContext().getKeyboardType(); // Any key down on an external keyboard should wake the device. // We don't do this for internal keyboards to prevent them from waking up in your pocket. // For internal keyboards and devices for which the default wake behavior is explicitly // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each // wake key individually. if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault && - !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) { + !(keyboardType != KeyboardType::ALPHABETIC && isMediaKey(keyCode))) { policyFlags |= POLICY_FLAG_WAKE; } @@ -341,8 +351,8 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } - out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(), - mSource, getDisplayId(), policyFlags, + out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId, mSource, + getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags, keyCode, scanCode, keyMetaState, downTime)); return out; diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index f2d3f4d7d8..bee2a99191 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -61,7 +61,6 @@ private: }; uint32_t mSource{}; - int32_t mKeyboardType{}; std::optional mKeyboardLayoutInfo; std::vector mKeyDowns{}; // keys that are down @@ -85,8 +84,7 @@ private: } mParameters{}; KeyboardInputMapper(InputDeviceContext& deviceContext, - const InputReaderConfiguration& readerConfig, uint32_t source, - int32_t keyboardType); + const InputReaderConfiguration& readerConfig, uint32_t source); void configureParameters(); void dumpParameters(std::string& dump) const; diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 8536ff0676..07fa59ff32 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -3314,6 +3314,10 @@ TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) { class KeyboardInputMapperTest : public InputMapperTest { protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD | + InputDeviceClass::ALPHAKEY); + } const std::string UNIQUE_ID = "local:0"; const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty"); void prepareDisplay(ui::Rotation orientation); @@ -3354,8 +3358,7 @@ void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper, TEST_F(KeyboardInputMapperTest, GetSources) { KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources()); } @@ -3370,8 +3373,7 @@ TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) { mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3471,8 +3473,7 @@ TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) { mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Key down by scan code. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1); @@ -3493,8 +3494,7 @@ TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); NotifyKeyArgs args; // Key down @@ -3516,8 +3516,7 @@ TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) { mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3557,8 +3556,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateD mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); prepareDisplay(ui::ROTATION_90); ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper, @@ -3579,8 +3577,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) { addConfigurationProperty("keyboard.orientationAware", "1"); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); prepareDisplay(ui::ROTATION_0); ASSERT_NO_FATAL_FAILURE( @@ -3651,8 +3648,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); NotifyKeyArgs args; // Display id should be LogicalDisplayId::INVALID without any display configuration. @@ -3677,8 +3673,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { addConfigurationProperty("keyboard.orientationAware", "1"); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); NotifyKeyArgs args; // Display id should be LogicalDisplayId::INVALID without any display configuration. @@ -3705,8 +3700,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) { TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1); ASSERT_EQ(1, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A)); @@ -3717,8 +3711,7 @@ TEST_F(KeyboardInputMapperTest, GetKeyCodeState) { TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) { KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z); ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y)) @@ -3730,8 +3723,7 @@ TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) { TEST_F(KeyboardInputMapperTest, GetScanCodeState) { KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1); ASSERT_EQ(1, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A)); @@ -3742,8 +3734,7 @@ TEST_F(KeyboardInputMapperTest, GetScanCodeState) { TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) { KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0); @@ -3762,8 +3753,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -3828,8 +3818,7 @@ TEST_F(KeyboardInputMapperTest, NoMetaStateWhenMetaKeysNotPresent) { mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Meta state should be AMETA_NONE after reset std::list unused = mapper.reset(ARBITRARY_TIME); @@ -3878,16 +3867,14 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) { mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID); KeyboardInputMapper& mapper2 = device2->constructAndAddMapper(SECOND_EVENTHUB_ID, mFakePolicy ->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + AINPUT_SOURCE_KEYBOARD); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -3949,8 +3936,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -4000,8 +3986,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) { device2->constructAndAddMapper(SECOND_EVENTHUB_ID, mFakePolicy ->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + AINPUT_SOURCE_KEYBOARD); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -4020,11 +4005,9 @@ TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); // Suppose we have two mappers. (DPAD + KEYBOARD) - constructAndAddMapper(AINPUT_SOURCE_DPAD, - AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_DPAD); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Initial metastate is AMETA_NONE. ASSERT_EQ(AMETA_NONE, mapper.getMetaState()); @@ -4042,8 +4025,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0); KeyboardInputMapper& mapper1 = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // keyboard 2. const std::string USB2 = "USB2"; @@ -4065,8 +4047,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) { device2->constructAndAddMapper(SECOND_EVENTHUB_ID, mFakePolicy ->getReaderConfiguration(), - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + AINPUT_SOURCE_KEYBOARD); std::list unused = device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -4122,8 +4103,7 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); // Key down by scan code. process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); NotifyKeyArgs args; @@ -4148,8 +4128,7 @@ TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) { } TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) { - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); std::list unused = mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), /*changes=*/{}); @@ -4180,8 +4159,7 @@ TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}); // Configuration - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); InputReaderConfiguration config; std::list unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{}); @@ -4192,8 +4170,7 @@ TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) { TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) { mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); NotifyKeyArgs args; // Key down @@ -4202,14 +4179,27 @@ TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) { ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags); } -// --- KeyboardInputMapperTest_ExternalDevice --- +// --- KeyboardInputMapperTest_ExternalAlphabeticDevice --- -class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest { +class KeyboardInputMapperTest_ExternalAlphabeticDevice : public InputMapperTest { protected: - void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); } + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD | + InputDeviceClass::ALPHAKEY | InputDeviceClass::EXTERNAL); + } +}; + +// --- KeyboardInputMapperTest_ExternalNonAlphabeticDevice --- + +class KeyboardInputMapperTest_ExternalNonAlphabeticDevice : public InputMapperTest { +protected: + void SetUp() override { + InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD | + InputDeviceClass::EXTERNAL); + } }; -TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) { +TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, WakeBehavior_AlphabeticKeyboard) { // For external devices, keys will trigger wake on key down. Media keys should also trigger // wake if triggered from external devices. @@ -4219,8 +4209,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); NotifyKeyArgs args; @@ -4248,7 +4237,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } -TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboard) { +TEST_F(KeyboardInputMapperTest_ExternalNonAlphabeticDevice, WakeBehavior_NonAlphabeticKeyboard) { // For external devices, keys will trigger wake on key down. Media keys should not trigger // wake if triggered from external non-alphaebtic keyboard (e.g. headsets). @@ -4257,8 +4246,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboa POLICY_FLAG_WAKE); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1); NotifyKeyArgs args; @@ -4278,7 +4266,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboa ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags); } -TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { +TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, DoNotWakeByDefaultBehavior) { // Tv Remote key's wake behavior is prescribed by the keylayout file. mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); @@ -4287,8 +4275,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) { addConfigurationProperty("keyboard.doNotWakeByDefault", "1"); KeyboardInputMapper& mapper = - constructAndAddMapper(AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1); NotifyKeyArgs args; diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index 6389cdc5fb..2d4e917691 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -75,8 +76,11 @@ public: MOCK_METHOD(void, setLastKeyDownTimestamp, (nsecs_t when)); MOCK_METHOD(nsecs_t, getLastKeyDownTimestamp, ()); + KeyboardClassifier& getKeyboardClassifier() override { return *mClassifier; }; + private: int32_t mGeneration = 0; + std::unique_ptr mClassifier = std::make_unique(); }; class MockEventHubInterface : public EventHubInterface { diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index 031b77d67a..ada841d28d 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -67,8 +67,7 @@ protected: EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get())); mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration, - AINPUT_SOURCE_KEYBOARD, - AINPUT_KEYBOARD_TYPE_ALPHABETIC); + AINPUT_SOURCE_KEYBOARD); } void testPointerVisibilityForKeys(const std::vector& keyCodes, bool expectVisible) { diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp index 922cbdfb87..3b3f8d25c3 100644 --- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp +++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp @@ -50,11 +50,10 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) { FuzzInputReaderContext context(eventHub, fdp); InputDevice device = getFuzzedInputDevice(*fdp, &context); - KeyboardInputMapper& mapper = getMapperForDevice< - ThreadSafeFuzzedDataProvider, - KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{}, - /*source=*/fdp->ConsumeIntegral(), - /*keyboardType=*/fdp->ConsumeIntegral()); + KeyboardInputMapper& mapper = + getMapperForDevice(*fdp.get(), device, InputReaderConfiguration{}, + /*source=*/fdp->ConsumeIntegral()); // Loop through mapper operations until randomness is exhausted. while (fdp->remaining_bytes() > 0) { diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h index 25f2f2ec76..ff425ddfb7 100644 --- a/services/inputflinger/tests/fuzzers/MapperHelpers.h +++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h @@ -338,9 +338,11 @@ public: void setLastKeyDownTimestamp(nsecs_t when) { mLastKeyDownTimestamp = when; }; nsecs_t getLastKeyDownTimestamp() { return mLastKeyDownTimestamp; }; + KeyboardClassifier& getKeyboardClassifier() override { return *mClassifier; } private: nsecs_t mLastKeyDownTimestamp; + std::unique_ptr mClassifier = std::make_unique(); }; template -- GitLab From 2e73b2a3aae5f9e5a3d649139aad01921da7bfba Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Fri, 7 Jun 2024 17:45:19 +0000 Subject: [PATCH 415/465] Implement basic functionality for rust keyboard classifier DD: go/project-imposter-android This CL includes: - Basic categorization into alphabetic and non-alphabetic - Updating categorization based on key presses Test: atest --host libinput_rust_test Test: atest inputflinger_tests Bug: 263559234 Flag: com.android.input.flags.enable_keyboard_classifier Change-Id: I02bc2802233af69494753121cbeb091343b3f9cf --- libs/input/rust/keyboard_classifier.rs | 311 +++++++++++++++++++++++-- 1 file changed, 297 insertions(+), 14 deletions(-) diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs index bd3a51cb51..1063fac664 100644 --- a/libs/input/rust/keyboard_classifier.rs +++ b/libs/input/rust/keyboard_classifier.rs @@ -17,14 +17,39 @@ //! Contains the KeyboardClassifier, that tries to identify whether an Input device is an //! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device //! in order to verify/change the inferred keyboard type. +//! +//! Initial classification: +//! - If DeviceClass includes Dpad, Touch, Cursor, MultiTouch, ExternalStylus, Touchpad, Dpad, +//! Gamepad, Switch, Joystick, RotaryEncoder => KeyboardType::NonAlphabetic +//! - Otherwise if DeviceClass has Keyboard and not AlphabeticKey => KeyboardType::NonAlphabetic +//! - Otherwise if DeviceClass has both Keyboard and AlphabeticKey => KeyboardType::Alphabetic +//! +//! On process keys: +//! - If KeyboardType::NonAlphabetic and we receive alphabetic key event, then change type to +//! KeyboardType::Alphabetic. Once changed, no further changes. (i.e. verified = true) +//! - TODO(b/263559234): If KeyboardType::Alphabetic and we don't receive any alphabetic key event +//! across multiple device connections in a time period, then change type to +//! KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic +//! (i.e. verified = false). +//! +//! TODO(b/263559234): Data store implementation to store information about past classification use crate::input::{DeviceId, InputDevice, KeyboardType}; -use crate::ModifierState; +use crate::{DeviceClass, ModifierState}; +use std::collections::HashMap; /// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic /// keyboard or non-alphabetic keyboard #[derive(Default)] -pub struct KeyboardClassifier {} +pub struct KeyboardClassifier { + device_map: HashMap, +} + +struct KeyboardInfo { + _device: InputDevice, + keyboard_type: KeyboardType, + is_finalized: bool, +} impl KeyboardClassifier { /// Create a new KeyboardClassifier @@ -33,30 +58,288 @@ impl KeyboardClassifier { } /// Adds keyboard to KeyboardClassifier - pub fn notify_keyboard_changed(&mut self, _device: InputDevice) { - // TODO(b/263559234): Implement method + pub fn notify_keyboard_changed(&mut self, device: InputDevice) { + let (keyboard_type, is_finalized) = self.classify_keyboard(&device); + self.device_map.insert( + device.device_id, + KeyboardInfo { _device: device, keyboard_type, is_finalized }, + ); } /// Get keyboard type for a tracked keyboard in KeyboardClassifier - pub fn get_keyboard_type(&self, _device_id: DeviceId) -> KeyboardType { - // TODO(b/263559234): Implement method - KeyboardType::None + pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType { + return if let Some(keyboard) = self.device_map.get(&device_id) { + keyboard.keyboard_type + } else { + KeyboardType::None + }; } /// Tells if keyboard type classification is finalized. Once finalized the classification can't /// change until device is reconnected again. - pub fn is_finalized(&self, _device_id: DeviceId) -> bool { - // TODO(b/263559234): Implement method - false + /// + /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or + /// allowlist that are explicitly categorized and won't change with future key events + pub fn is_finalized(&self, device_id: DeviceId) -> bool { + return if let Some(keyboard) = self.device_map.get(&device_id) { + keyboard.is_finalized + } else { + false + }; } /// Process a key event and change keyboard type if required. + /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic + /// - If an alphabetic key occurs, the keyboard type will change to Alphabetic pub fn process_key( &mut self, - _device_id: DeviceId, - _evdev_code: i32, - _modifier_state: ModifierState, + device_id: DeviceId, + evdev_code: i32, + modifier_state: ModifierState, ) { - // TODO(b/263559234): Implement method + if let Some(keyboard) = self.device_map.get_mut(&device_id) { + // Ignore all key events with modifier state since they can be macro shortcuts used by + // some non-keyboard peripherals like TV remotes, game controllers, etc. + if modifier_state.bits() != 0 { + return; + } + if Self::is_alphabetic_key(&evdev_code) { + keyboard.keyboard_type = KeyboardType::Alphabetic; + keyboard.is_finalized = true; + } + } + } + + fn classify_keyboard(&self, device: &InputDevice) -> (KeyboardType, bool) { + // This should never happen but having keyboard device class is necessary to be classified + // as any type of keyboard. + if !device.classes.contains(DeviceClass::Keyboard) { + return (KeyboardType::None, true); + } + // Normal classification for internal and virtual keyboards + if !device.classes.contains(DeviceClass::External) + || device.classes.contains(DeviceClass::Virtual) + { + return if device.classes.contains(DeviceClass::AlphabeticKey) { + (KeyboardType::Alphabetic, true) + } else { + (KeyboardType::NonAlphabetic, true) + }; + } + // Any composite device with multiple device classes should be categorized as non-alphabetic + // keyboard initially + if device.classes.contains(DeviceClass::Touch) + || device.classes.contains(DeviceClass::Cursor) + || device.classes.contains(DeviceClass::MultiTouch) + || device.classes.contains(DeviceClass::ExternalStylus) + || device.classes.contains(DeviceClass::Touchpad) + || device.classes.contains(DeviceClass::Dpad) + || device.classes.contains(DeviceClass::Gamepad) + || device.classes.contains(DeviceClass::Switch) + || device.classes.contains(DeviceClass::Joystick) + || device.classes.contains(DeviceClass::RotaryEncoder) + { + // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the + // kernel, we no longer need to process key events to verify. + return ( + KeyboardType::NonAlphabetic, + !device.classes.contains(DeviceClass::AlphabeticKey), + ); + } + // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard + if device.classes.contains(DeviceClass::AlphabeticKey) { + (KeyboardType::Alphabetic, true) + } else { + // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the + // kernel, we no longer need to process key events to verify. + (KeyboardType::NonAlphabetic, true) + } + } + + fn is_alphabetic_key(evdev_code: &i32) -> bool { + // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ]) + (16..=27).contains(evdev_code) + // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `) + || (30..=41).contains(evdev_code) + // Keyboard alphabetic row 3 (\ Z X C V B N M , . /) + || (43..=53).contains(evdev_code) + } +} + +#[cfg(test)] +mod tests { + use crate::input::{DeviceId, InputDevice, KeyboardType}; + use crate::keyboard_classifier::KeyboardClassifier; + use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier}; + + static DEVICE_ID: DeviceId = DeviceId(1); + static KEY_A: i32 = 30; + static KEY_1: i32 = 2; + + #[test] + fn classify_external_alphabetic_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic); + assert!(classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_external_non_alphabetic_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier + .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External)); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_mouse_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Cursor + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_touchpad_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Touchpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_stylus_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::ExternalStylus + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_dpad_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_joystick_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Joystick + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn classify_gamepad_pretending_as_keyboard() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Gamepad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn reclassify_keyboard_on_alphabetic_key_event() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + + // on alphabetic key event + classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic); + assert!(classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn dont_reclassify_keyboard_on_non_alphabetic_key_event() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + + // on number key event + classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + #[test] + fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() { + let mut classifier = KeyboardClassifier::new(); + classifier.notify_keyboard_changed(create_device( + DeviceClass::Keyboard + | DeviceClass::Dpad + | DeviceClass::AlphabeticKey + | DeviceClass::External, + )); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + + classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn); + assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); + assert!(!classifier.is_finalized(DEVICE_ID)); + } + + fn create_device(classes: DeviceClass) -> InputDevice { + InputDevice { + device_id: DEVICE_ID, + identifier: RustInputDeviceIdentifier { + name: "test_device".to_string(), + location: "location".to_string(), + unique_id: "unique_id".to_string(), + bus: 123, + vendor: 234, + product: 345, + version: 567, + descriptor: "descriptor".to_string(), + }, + classes, + } } } -- GitLab From 8c42cf19d10c9a685e8d6a88c9057435dd738a6b Mon Sep 17 00:00:00 2001 From: Melody Hsu Date: Wed, 5 Jun 2024 00:07:03 +0000 Subject: [PATCH 416/465] Remove main thread double hop from screenshots The SF main thread is accessed twice during screenshots, which leads to possible inconsistent states between jumps onto the main thread. Removing the double hop preserves correctness and reduces the amount of computation taking place on the main thread. The only time the main thread should be accessed should be when getting layer snapshots. All other work related to screenshots can take place in a binder thread. Bug: b/294936197, b/285553970 Test: atest SurfaceFlinger_test Flag: com.android.graphics.surfaceflinger.flags.single_hop_screenshot Change-Id: If9fd36f82c2d550bc0821b52fef3ea88b8099116 --- .../surfaceflinger/RegionSamplingThread.cpp | 33 ++- services/surfaceflinger/SurfaceFlinger.cpp | 256 +++++++++++++----- services/surfaceflinger/SurfaceFlinger.h | 35 ++- .../surfaceflinger/common/FlagManager.cpp | 2 + .../common/include/common/FlagManager.h | 1 + .../surfaceflinger_flags_new.aconfig | 11 + .../tests/unittests/TestableSurfaceFlinger.h | 7 +- 7 files changed, 250 insertions(+), 95 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 59eb7f5d4a..5add290e96 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -348,17 +348,30 @@ void RegionSamplingThread::captureSample() { constexpr bool kGrayscale = false; constexpr bool kIsProtected = false; - if (const auto fenceResult = - mFlinger.captureScreenshot(SurfaceFlinger::RenderAreaBuilderVariant( - std::in_place_type, - sampledBounds, sampledBounds.getSize(), - ui::Dataspace::V0_SRGB, - kHintForSeamlessTransition, - true /* captureSecureLayers */, displayWeak), - getLayerSnapshotsFn, buffer, kRegionSampling, kGrayscale, - kIsProtected, nullptr) + SurfaceFlinger::RenderAreaBuilderVariant + renderAreaBuilder(std::in_place_type, sampledBounds, + sampledBounds.getSize(), ui::Dataspace::V0_SRGB, + kHintForSeamlessTransition, true /* captureSecureLayers */, + displayWeak); + + FenceResult fenceResult; + if (FlagManager::getInstance().single_hop_screenshot() && + FlagManager::getInstance().ce_fence_promise()) { + std::vector> layerFEs; + auto displayState = + mFlinger.getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, + getLayerSnapshotsFn, layerFEs); + fenceResult = + mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale, + kIsProtected, nullptr, displayState, layerFEs) .get(); - fenceResult.ok()) { + } else { + fenceResult = + mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, buffer, + kRegionSampling, kGrayscale, kIsProtected, nullptr) + .get(); + } + if (fenceResult.ok()) { fenceResult.value()->waitForever(LOG_TAG); } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index fb350107d8..a48a1b3a0b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8110,12 +8110,15 @@ void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* laye owningLayer->prepareReleaseCallbacks(std::move(futureFence), layerStack); } -bool SurfaceFlinger::layersHasProtectedLayer( - const std::vector>>& layers) const { +// Loop over all visible layers to see whether there's any protected layer. A protected layer is +// typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer. +// A protected layer has no implication on whether it's secure, which is explicitly set by +// application to avoid being screenshot or drawn via unsecure display. +bool SurfaceFlinger::layersHasProtectedLayer(const std::vector>& layers) const { bool protectedLayerFound = false; - for (auto& [_, layerFe] : layers) { + for (auto& layerFE : layers) { protectedLayerFound |= - (layerFe->mSnapshot->isVisible && layerFe->mSnapshot->hasProtectedContent); + (layerFE->mSnapshot->isVisible && layerFE->mSnapshot->hasProtectedContent); if (protectedLayerFound) { break; } @@ -8123,6 +8126,26 @@ bool SurfaceFlinger::layersHasProtectedLayer( return protectedLayerFound; } +// Getting layer snapshots and display should take place on main thread. +// Accessing display requires mStateLock, and contention for this lock +// is reduced when grabbed from the main thread, thus also reducing +// risk of deadlocks. +std::optional +SurfaceFlinger::getDisplayAndLayerSnapshotsFromMainThread( + RenderAreaBuilderVariant& renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn, + std::vector>& layerFEs) { + return mScheduler + ->schedule([=, this, &renderAreaBuilder, &layerFEs]() REQUIRES(kMainThreadContext) { + auto layers = getLayerSnapshotsFn(); + for (auto& [layer, layerFE] : layers) { + attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); + } + layerFEs = extractLayerFEs(layers); + return getDisplayStateFromRenderAreaBuilder(renderAreaBuilder); + }) + .get(); +} + void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn, ui::Size bufferSize, ui::PixelFormat reqPixelFormat, @@ -8138,47 +8161,85 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuil return; } - // Loop over all visible layers to see whether there's any protected layer. A protected layer is - // typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer. - // A protected layer has no implication on whether it's secure, which is explicitly set by - // application to avoid being screenshot or drawn via unsecure display. - const bool supportsProtected = getRenderEngine().supportsProtectedContent(); - bool hasProtectedLayer = false; - if (allowProtected && supportsProtected) { - auto layers = mScheduler->schedule([=]() { return getLayerSnapshotsFn(); }).get(); - hasProtectedLayer = layersHasProtectedLayer(layers); - } - const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected; - const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | - GRALLOC_USAGE_HW_TEXTURE | - (isProtected ? GRALLOC_USAGE_PROTECTED - : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN); - sp buffer = - getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(), - static_cast(reqPixelFormat), - 1 /* layerCount */, usage, "screenshot"); - - const status_t bufferStatus = buffer->initCheck(); - if (bufferStatus != OK) { - // Animations may end up being really janky, but don't crash here. - // Otherwise an irreponsible process may cause an SF crash by allocating - // too much. - ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus); - invokeScreenCaptureError(bufferStatus, captureListener); - return; + if (FlagManager::getInstance().single_hop_screenshot() && + FlagManager::getInstance().ce_fence_promise()) { + std::vector> layerFEs; + auto displayState = + getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, + layerFEs); + + const bool supportsProtected = getRenderEngine().supportsProtectedContent(); + bool hasProtectedLayer = false; + if (allowProtected && supportsProtected) { + hasProtectedLayer = layersHasProtectedLayer(layerFEs); + } + const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected; + const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE | + (isProtected ? GRALLOC_USAGE_PROTECTED + : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN); + sp buffer = + getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(), + static_cast(reqPixelFormat), + 1 /* layerCount */, usage, "screenshot"); + + const status_t bufferStatus = buffer->initCheck(); + if (bufferStatus != OK) { + // Animations may end up being really janky, but don't crash here. + // Otherwise an irreponsible process may cause an SF crash by allocating + // too much. + ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus); + invokeScreenCaptureError(bufferStatus, captureListener); + return; + } + const std::shared_ptr texture = std::make_shared< + renderengine::impl::ExternalTexture>(buffer, getRenderEngine(), + renderengine::impl::ExternalTexture::Usage:: + WRITEABLE); + auto futureFence = + captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */, grayscale, + isProtected, captureListener, displayState, layerFEs); + futureFence.get(); + + } else { + const bool supportsProtected = getRenderEngine().supportsProtectedContent(); + bool hasProtectedLayer = false; + if (allowProtected && supportsProtected) { + auto layers = mScheduler->schedule([=]() { return getLayerSnapshotsFn(); }).get(); + hasProtectedLayer = layersHasProtectedLayer(extractLayerFEs(layers)); + } + const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected; + const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE | + (isProtected ? GRALLOC_USAGE_PROTECTED + : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN); + sp buffer = + getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(), + static_cast(reqPixelFormat), + 1 /* layerCount */, usage, "screenshot"); + + const status_t bufferStatus = buffer->initCheck(); + if (bufferStatus != OK) { + // Animations may end up being really janky, but don't crash here. + // Otherwise an irreponsible process may cause an SF crash by allocating + // too much. + ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus); + invokeScreenCaptureError(bufferStatus, captureListener); + return; + } + const std::shared_ptr texture = std::make_shared< + renderengine::impl::ExternalTexture>(buffer, getRenderEngine(), + renderengine::impl::ExternalTexture::Usage:: + WRITEABLE); + auto futureFence = captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, texture, + false /* regionSampling */, grayscale, + isProtected, captureListener); + futureFence.get(); } - const std::shared_ptr texture = std::make_shared< - renderengine::impl::ExternalTexture>(buffer, getRenderEngine(), - renderengine::impl::ExternalTexture::Usage:: - WRITEABLE); - auto futureFence = - captureScreenshot(renderAreaBuilder, getLayerSnapshotsFn, texture, - false /* regionSampling */, grayscale, isProtected, captureListener); - futureFence.get(); } -const sp SurfaceFlinger::getRenderAreaDisplay( - RenderAreaBuilderVariant& renderAreaBuilder, OutputCompositionState& state) { +std::optional +SurfaceFlinger::getDisplayStateFromRenderAreaBuilder(RenderAreaBuilderVariant& renderAreaBuilder) { sp display = nullptr; { Mutex::Autolock lock(mStateLock); @@ -8207,24 +8268,64 @@ const sp SurfaceFlinger::getRenderAreaDisplay( } if (display != nullptr) { - state = display->getCompositionDisplay()->getState(); + return std::optional{display->getCompositionDisplay()->getState()}; } } - return display; + return std::nullopt; } -std::vector>> -SurfaceFlinger::getLayerSnapshotsFromMainThread(GetLayerSnapshotsFunction getLayerSnapshotsFn) { - auto layers = getLayerSnapshotsFn(); - if (FlagManager::getInstance().ce_fence_promise()) { - for (auto& [layer, layerFE] : layers) { - attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); - } +std::vector> SurfaceFlinger::extractLayerFEs( + const std::vector>>& layers) const { + std::vector> layerFEs; + layerFEs.reserve(layers.size()); + for (const auto& [_, layerFE] : layers) { + layerFEs.push_back(layerFE); } - return layers; + return layerFEs; } ftl::SharedFuture SurfaceFlinger::captureScreenshot( + const RenderAreaBuilderVariant& renderAreaBuilder, + const std::shared_ptr& buffer, bool regionSampling, + bool grayscale, bool isProtected, const sp& captureListener, + std::optional& displayState, std::vector>& layerFEs) { + ATRACE_CALL(); + + ScreenCaptureResults captureResults; + std::unique_ptr renderArea = + std::visit([](auto&& arg) -> std::unique_ptr { return arg.build(); }, + renderAreaBuilder); + + if (!renderArea) { + ALOGW("Skipping screen capture because of invalid render area."); + if (captureListener) { + captureResults.fenceResult = base::unexpected(NO_MEMORY); + captureListener->onScreenCaptureCompleted(captureResults); + } + return ftl::yield(base::unexpected(NO_ERROR)).share(); + } + + // Empty vector needed to pass into renderScreenImpl for legacy path + std::vector>> layers; + ftl::SharedFuture renderFuture = + renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected, + captureResults, displayState, layers, layerFEs); + + if (captureListener) { + // Defer blocking on renderFuture back to the Binder thread. + return ftl::Future(std::move(renderFuture)) + .then([captureListener, captureResults = std::move(captureResults)]( + FenceResult fenceResult) mutable -> FenceResult { + captureResults.fenceResult = std::move(fenceResult); + captureListener->onScreenCaptureCompleted(captureResults); + return base::unexpected(NO_ERROR); + }) + .share(); + } + return renderFuture; +} + +ftl::SharedFuture SurfaceFlinger::captureScreenshotLegacy( RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, const sp& captureListener) { @@ -8232,10 +8333,13 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES( kMainThreadContext) mutable -> ftl::SharedFuture { - auto layers = getLayerSnapshotsFromMainThread(getLayerSnapshotsFn); - - OutputCompositionState state; - const auto display = getRenderAreaDisplay(renderAreaBuilder, state); + auto layers = getLayerSnapshotsFn(); + if (FlagManager::getInstance().ce_fence_promise()) { + for (auto& [layer, layerFE] : layers) { + attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK); + } + } + auto displayState = getDisplayStateFromRenderAreaBuilder(renderAreaBuilder); ScreenCaptureResults captureResults; std::unique_ptr renderArea = @@ -8251,9 +8355,10 @@ ftl::SharedFuture SurfaceFlinger::captureScreenshot( return ftl::yield(base::unexpected(NO_ERROR)).share(); } + auto layerFEs = extractLayerFEs(layers); ftl::SharedFuture renderFuture = renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, - isProtected, captureResults, display, state, layers); + isProtected, captureResults, displayState, layers, layerFEs); if (captureListener) { // Defer blocking on renderFuture back to the Binder thread. @@ -8286,11 +8391,11 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( std::unique_ptr renderArea, const std::shared_ptr& buffer, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults& captureResults, - const sp display, const OutputCompositionState& state, - std::vector>>& layers) { + std::optional& displayState, + std::vector>>& layers, std::vector>& layerFEs) { ATRACE_CALL(); - for (auto& [_, layerFE] : layers) { + for (auto& layerFE : layerFEs) { frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get(); captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure); captureResults.capturedHdrLayers |= isHdrLayer(*snapshot); @@ -8313,7 +8418,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() && !renderArea->getHintForSeamlessTransition(); - if (display != nullptr) { + if (displayState) { + const auto& state = displayState.value(); captureResults.capturedDataspace = pickBestDataspace(requestedDataspace, state, captureResults.capturedHdrLayers, renderArea->getHintForSeamlessTransition()); @@ -8348,18 +8454,18 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( captureResults.buffer = capturedBuffer->getBuffer(); ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK}; - if (!layers.empty()) { - const sp& layerFE = layers.back().second; + if (!layerFEs.empty()) { + const sp& layerFE = layerFEs.back(); layerStack = layerFE->getCompositionState()->outputFilter.layerStack; } - auto copyLayerFEs = [&layers]() { - std::vector> layerFEs; - layerFEs.reserve(layers.size()); - for (const auto& [_, layerFE] : layers) { - layerFEs.push_back(layerFE); + auto copyLayerFEs = [&layerFEs]() { + std::vector> ceLayerFEs; + ceLayerFEs.reserve(layerFEs.size()); + for (const auto& layerFE : layerFEs) { + ceLayerFEs.push_back(layerFE); } - return layerFEs; + return ceLayerFEs; }; auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace, @@ -8428,8 +8534,16 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( // // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call // to CompositionEngine::present. - auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share() - : ftl::yield(present()).share(); + ftl::SharedFuture presentFuture; + if (FlagManager::getInstance().single_hop_screenshot() && + FlagManager::getInstance().ce_fence_promise()) { + presentFuture = mRenderEngine->isThreaded() + ? ftl::yield(present()).share() + : mScheduler->schedule(std::move(present)).share(); + } else { + presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share() + : ftl::yield(present()).share(); + } if (!FlagManager::getInstance().ce_fence_promise()) { for (auto& [layer, layerFE] : layers) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 209d9bcfe6..12307172f7 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -892,22 +892,35 @@ private: void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack); // Checks if a protected layer exists in a list of layers. - bool layersHasProtectedLayer(const std::vector>>& layers) const; + bool layersHasProtectedLayer(const std::vector>& layers) const; + + using OutputCompositionState = compositionengine::impl::OutputCompositionState; + + std::optional getDisplayAndLayerSnapshotsFromMainThread( + RenderAreaBuilderVariant& renderAreaBuilder, + GetLayerSnapshotsFunction getLayerSnapshotsFn, std::vector>& layerFEs); void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction, ui::Size bufferSize, ui::PixelFormat, bool allowProtected, bool grayscale, const sp&); - using OutputCompositionState = compositionengine::impl::OutputCompositionState; - - const sp getRenderAreaDisplay(RenderAreaBuilderVariant& renderAreaBuilder, - OutputCompositionState& state) - REQUIRES(kMainThreadContext); + std::optional getDisplayStateFromRenderAreaBuilder( + RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext); - std::vector>> getLayerSnapshotsFromMainThread( - GetLayerSnapshotsFunction getLayerSnapshotsFn) REQUIRES(kMainThreadContext); + // Legacy layer raw pointer is not safe to access outside the main thread. + // Creates a new vector consisting only of LayerFEs, which can be safely + // accessed outside the main thread. + std::vector> extractLayerFEs( + const std::vector>>& layers) const; ftl::SharedFuture captureScreenshot( + const RenderAreaBuilderVariant& renderAreaBuilder, + const std::shared_ptr& buffer, bool regionSampling, + bool grayscale, bool isProtected, const sp& captureListener, + std::optional& displayState, + std::vector>& layerFEs); + + ftl::SharedFuture captureScreenshotLegacy( RenderAreaBuilderVariant, GetLayerSnapshotsFunction, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, const sp&); @@ -916,9 +929,9 @@ private: std::unique_ptr, const std::shared_ptr&, bool regionSampling, bool grayscale, bool isProtected, ScreenCaptureResults&, - const sp display, const OutputCompositionState& state, - std::vector>>& layers) - REQUIRES(kMainThreadContext); + std::optional& displayState, + std::vector>>& layers, + std::vector>& layerFEs); // If the uid provided is not UNSET_UID, the traverse will skip any layers that don't have a // matching ownerUid diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp index b7ec6e0b26..4216771515 100644 --- a/services/surfaceflinger/common/FlagManager.cpp +++ b/services/surfaceflinger/common/FlagManager.cpp @@ -150,6 +150,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(override_trusted_overlay); DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache); DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine); + DUMP_READ_ONLY_FLAG(single_hop_screenshot); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -250,6 +251,7 @@ FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_s FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, ""); FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, ""); FLAG_MANAGER_READ_ONLY_FLAG(force_compile_graphite_renderengine, ""); +FLAG_MANAGER_READ_ONLY_FLAG(single_hop_screenshot, ""); /// Trunk stable server flags /// FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "") diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 8f98ed3777..22118ab585 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -89,6 +89,7 @@ public: bool override_trusted_overlay() const; bool flush_buffer_slots_to_uncache() const; bool force_compile_graphite_renderengine() const; + bool single_hop_screenshot() const; protected: // overridden for unit tests diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig index ee12325705..f4d4ee978d 100644 --- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig +++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig @@ -103,6 +103,17 @@ flag { is_fixed_read_only: true } # local_tonemap_screenshots +flag { + name: "single_hop_screenshot" + namespace: "window_surfaces" + description: "Only access SF main thread once during a screenshot" + bug: "285553970" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } + } # single_hop_screenshot + flag { name: "override_trusted_overlay" namespace: "window_surfaces" diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 7889096fe2..8c72a7dba5 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -494,12 +494,13 @@ public: ftl::FakeGuard guard(kMainThreadContext); ScreenCaptureResults captureResults; - SurfaceFlinger::OutputCompositionState state = display->getCompositionDisplay()->getState(); - auto layers = mFlinger->getLayerSnapshotsFromMainThread(getLayerSnapshotsFn); + auto displayState = std::optional{display->getCompositionDisplay()->getState()}; + auto layers = getLayerSnapshotsFn(); + auto layerFEs = mFlinger->extractLayerFEs(layers); return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling, false /* grayscale */, false /* isProtected */, - captureResults, display, state, layers); + captureResults, displayState, layers, layerFEs); } auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid, -- GitLab From 247dda20cbb8198774fe5122413f37b066b75dd5 Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Fri, 7 Jun 2024 21:06:40 +0000 Subject: [PATCH 417/465] switch over to use new storage read api instead of server_configurable_flags, this new read api lib will be needed for new codegened aconfig flag lib. Bug: 321077378 Test m and avd Change-Id: Ia345d4a798c1d7f1861512a3e9aa0ab5e72d407f --- libs/renderengine/tests/Android.bp | 1 + services/surfaceflinger/common/Android.bp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp index 06d543961b..0783714eb9 100644 --- a/libs/renderengine/tests/Android.bp +++ b/libs/renderengine/tests/Android.bp @@ -65,5 +65,6 @@ cc_test { "libui", "libutils", "server_configurable_flags", + "libaconfig_storage_read_api_cc", ], } diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp index 6b971a7597..c3594bcf55 100644 --- a/services/surfaceflinger/common/Android.bp +++ b/services/surfaceflinger/common/Android.bp @@ -17,6 +17,7 @@ cc_defaults { shared_libs: [ "libSurfaceFlingerProp", "server_configurable_flags", + "libaconfig_storage_read_api_cc", ], static_libs: [ "librenderengine_includes", @@ -56,6 +57,7 @@ cc_defaults { name: "libsurfaceflinger_common_deps", shared_libs: [ "server_configurable_flags", + "libaconfig_storage_read_api_cc", ], static_libs: [ "libsurfaceflinger_common", @@ -69,6 +71,7 @@ cc_defaults { name: "libsurfaceflinger_common_test_deps", shared_libs: [ "server_configurable_flags", + "libaconfig_storage_read_api_cc", ], static_libs: [ "libsurfaceflinger_common_test", -- GitLab From 770b6e4a1c99e06473e428308026706fef7fc17c Mon Sep 17 00:00:00 2001 From: Vaibhav Devmurari Date: Mon, 10 Jun 2024 10:29:26 +0000 Subject: [PATCH 418/465] Cleanup: Use ModifierState enum in input.rs in sticky keys filter Test: none Bug: 245989146 Change-Id: If2c46c9fb228ebb35bc7c66a4d58bf27ec000f4b --- libs/input/rust/input.rs | 2 +- services/inputflinger/rust/Android.bp | 1 + services/inputflinger/rust/input_filter.rs | 35 ++-- .../inputflinger/rust/sticky_keys_filter.rs | 173 +++++++++--------- 4 files changed, 110 insertions(+), 101 deletions(-) diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index 72b421cffe..0574245fa6 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -272,7 +272,7 @@ bitflags! { bitflags! { /// Modifier state flags - #[derive(Debug, PartialEq)] + #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] pub struct ModifierState: u32 { /// No meta keys are pressed const None = input_bindgen::AMETA_NONE; diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp index 255c7eb679..5b7cc2d432 100644 --- a/services/inputflinger/rust/Android.bp +++ b/services/inputflinger/rust/Android.bp @@ -47,6 +47,7 @@ rust_defaults { "liblog_rust", "liblogger", "libnix", + "libinput_rust", ], host_supported: true, } diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs index 6df339ed67..8b44af38db 100644 --- a/services/inputflinger/rust/input_filter.rs +++ b/services/inputflinger/rust/input_filter.rs @@ -31,6 +31,7 @@ use crate::bounce_keys_filter::BounceKeysFilter; use crate::input_filter_thread::InputFilterThread; use crate::slow_keys_filter::SlowKeysFilter; use crate::sticky_keys_filter::StickyKeysFilter; +use input::ModifierState; use log::{error, info}; use std::sync::{Arc, Mutex, RwLock}; @@ -169,12 +170,15 @@ impl ModifierStateListener { Self(callbacks) } - pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) { - let _ = self - .0 - .read() - .unwrap() - .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32); + pub fn modifier_state_changed( + &self, + modifier_state: ModifierState, + locked_modifier_state: ModifierState, + ) { + let _ = self.0.read().unwrap().onModifierStateChanged( + modifier_state.bits() as i32, + locked_modifier_state.bits() as i32, + ); } } @@ -396,14 +400,15 @@ pub mod test_callbacks { IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback}, KeyEvent::KeyEvent, }; + use input::ModifierState; use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId}; use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard}; use std::time::Duration; #[derive(Default)] struct TestCallbacksInner { - last_modifier_state: u32, - last_locked_modifier_state: u32, + last_modifier_state: ModifierState, + last_locked_modifier_state: ModifierState, last_event: Option, test_thread: Option, } @@ -428,15 +433,15 @@ pub mod test_callbacks { pub fn clear(&mut self) { self.inner().last_event = None; - self.inner().last_modifier_state = 0; - self.inner().last_locked_modifier_state = 0; + self.inner().last_modifier_state = ModifierState::None; + self.inner().last_locked_modifier_state = ModifierState::None; } - pub fn get_last_modifier_state(&self) -> u32 { + pub fn get_last_modifier_state(&self) -> ModifierState { self.0.read().unwrap().last_modifier_state } - pub fn get_last_locked_modifier_state(&self) -> u32 { + pub fn get_last_locked_modifier_state(&self) -> ModifierState { self.0.read().unwrap().last_locked_modifier_state } @@ -459,8 +464,10 @@ pub mod test_callbacks { modifier_state: i32, locked_modifier_state: i32, ) -> std::result::Result<(), binder::Status> { - self.inner().last_modifier_state = modifier_state as u32; - self.inner().last_locked_modifier_state = locked_modifier_state as u32; + self.inner().last_modifier_state = + ModifierState::from_bits(modifier_state as u32).unwrap(); + self.inner().last_locked_modifier_state = + ModifierState::from_bits(locked_modifier_state as u32).unwrap(); Result::Ok(()) } diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs index 6c2277c813..6c7c7fba39 100644 --- a/services/inputflinger/rust/sticky_keys_filter.rs +++ b/services/inputflinger/rust/sticky_keys_filter.rs @@ -23,6 +23,7 @@ use crate::input_filter::{Filter, ModifierStateListener}; use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, }; +use input::ModifierState; use std::collections::HashSet; // Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h @@ -40,20 +41,6 @@ const KEYCODE_META_RIGHT: i32 = 118; const KEYCODE_FUNCTION: i32 = 119; const KEYCODE_NUM_LOCK: i32 = 143; -// Modifier states: values are from /frameworks/native/include/android/input.h -const META_ALT_ON: u32 = 0x02; -const META_ALT_LEFT_ON: u32 = 0x10; -const META_ALT_RIGHT_ON: u32 = 0x20; -const META_SHIFT_ON: u32 = 0x01; -const META_SHIFT_LEFT_ON: u32 = 0x40; -const META_SHIFT_RIGHT_ON: u32 = 0x80; -const META_CTRL_ON: u32 = 0x1000; -const META_CTRL_LEFT_ON: u32 = 0x2000; -const META_CTRL_RIGHT_ON: u32 = 0x4000; -const META_META_ON: u32 = 0x10000; -const META_META_LEFT_ON: u32 = 0x20000; -const META_META_RIGHT_ON: u32 = 0x40000; - pub struct StickyKeysFilter { next: Box, listener: ModifierStateListener, @@ -61,11 +48,11 @@ pub struct StickyKeysFilter { contributing_devices: HashSet, /// State describing the current enabled modifiers. This contain both locked and non-locked /// modifier state bits. - modifier_state: u32, + modifier_state: ModifierState, /// State describing the current locked modifiers. These modifiers will not be cleared on a /// non-modifier key press. They will be cleared only if the locked modifier key is pressed /// again. - locked_modifier_state: u32, + locked_modifier_state: ModifierState, } impl StickyKeysFilter { @@ -78,8 +65,8 @@ impl StickyKeysFilter { next, listener, contributing_devices: HashSet::new(), - modifier_state: 0, - locked_modifier_state: 0, + modifier_state: ModifierState::None, + locked_modifier_state: ModifierState::None, } } } @@ -93,12 +80,12 @@ impl Filter for StickyKeysFilter { // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with // the KeyEvent. - let old_modifier_state = event.metaState as u32; + let old_modifier_state = ModifierState::from_bits(event.metaState as u32).unwrap(); let mut new_event = *event; // Send the current modifier state with the key event before clearing non-locked // modifier state new_event.metaState = - (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32; + (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state).bits() as i32; self.next.notify_key(&new_event); if up && !is_modifier_key(event.keyCode) { modifier_state = @@ -110,10 +97,10 @@ impl Filter for StickyKeysFilter { // If ephemeral modifier key, capture the key and update the sticky modifier states let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode); let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode); - if locked_modifier_state & modifier_key_mask != 0 { + if locked_modifier_state & modifier_key_mask != ModifierState::None { locked_modifier_state &= !symmetrical_modifier_key_mask; modifier_state &= !symmetrical_modifier_key_mask; - } else if modifier_key_mask & modifier_state != 0 { + } else if modifier_key_mask & modifier_state != ModifierState::None { locked_modifier_state |= modifier_key_mask; modifier_state = (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask; @@ -134,11 +121,12 @@ impl Filter for StickyKeysFilter { // Clear state if all contributing devices removed self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId)); if self.contributing_devices.is_empty() - && (self.modifier_state != 0 || self.locked_modifier_state != 0) + && (self.modifier_state != ModifierState::None + || self.locked_modifier_state != ModifierState::None) { - self.modifier_state = 0; - self.locked_modifier_state = 0; - self.listener.modifier_state_changed(0, 0); + self.modifier_state = ModifierState::None; + self.locked_modifier_state = ModifierState::None; + self.listener.modifier_state_changed(ModifierState::None, ModifierState::None); } self.next.notify_devices_changed(device_infos); } @@ -181,51 +169,53 @@ fn is_ephemeral_modifier_key(keycode: i32) -> bool { ) } -fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 { +fn get_ephemeral_modifier_key_mask(keycode: i32) -> ModifierState { match keycode { - KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON, - KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON, - KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON, - KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON, - KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON, - KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON, - KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON, - KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON, - _ => 0, + KEYCODE_ALT_LEFT => ModifierState::AltLeftOn | ModifierState::AltOn, + KEYCODE_ALT_RIGHT => ModifierState::AltRightOn | ModifierState::AltOn, + KEYCODE_SHIFT_LEFT => ModifierState::ShiftLeftOn | ModifierState::ShiftOn, + KEYCODE_SHIFT_RIGHT => ModifierState::ShiftRightOn | ModifierState::ShiftOn, + KEYCODE_CTRL_LEFT => ModifierState::CtrlLeftOn | ModifierState::CtrlOn, + KEYCODE_CTRL_RIGHT => ModifierState::CtrlRightOn | ModifierState::CtrlOn, + KEYCODE_META_LEFT => ModifierState::MetaLeftOn | ModifierState::MetaOn, + KEYCODE_META_RIGHT => ModifierState::MetaRightOn | ModifierState::MetaOn, + _ => ModifierState::None, } } /// Modifier mask including both left and right versions of a modifier key. -fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 { +fn get_symmetrical_modifier_key_mask(keycode: i32) -> ModifierState { match keycode { - KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON, + KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => { + ModifierState::AltLeftOn | ModifierState::AltRightOn | ModifierState::AltOn + } KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => { - META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON + ModifierState::ShiftLeftOn | ModifierState::ShiftRightOn | ModifierState::ShiftOn } KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => { - META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON + ModifierState::CtrlLeftOn | ModifierState::CtrlRightOn | ModifierState::CtrlOn } KEYCODE_META_LEFT | KEYCODE_META_RIGHT => { - META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON + ModifierState::MetaLeftOn | ModifierState::MetaRightOn | ModifierState::MetaOn } - _ => 0, + _ => ModifierState::None, } } -fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 { +fn clear_ephemeral_modifier_state(modifier_state: ModifierState) -> ModifierState { modifier_state - & !(META_ALT_LEFT_ON - | META_ALT_RIGHT_ON - | META_ALT_ON - | META_SHIFT_LEFT_ON - | META_SHIFT_RIGHT_ON - | META_SHIFT_ON - | META_CTRL_LEFT_ON - | META_CTRL_RIGHT_ON - | META_CTRL_ON - | META_META_LEFT_ON - | META_META_RIGHT_ON - | META_META_ON) + & !(ModifierState::AltLeftOn + | ModifierState::AltRightOn + | ModifierState::AltOn + | ModifierState::ShiftLeftOn + | ModifierState::ShiftRightOn + | ModifierState::ShiftOn + | ModifierState::CtrlLeftOn + | ModifierState::CtrlRightOn + | ModifierState::CtrlOn + | ModifierState::MetaLeftOn + | ModifierState::MetaRightOn + | ModifierState::MetaOn) } #[cfg(test)] @@ -237,9 +227,7 @@ mod tests { StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK, KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT, KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT, - KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON, - META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON, - META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON, + KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, }; use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source; use binder::Strong; @@ -247,6 +235,7 @@ mod tests { DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction, }; + use input::ModifierState; use std::sync::{Arc, RwLock}; static DEVICE_ID: i32 = 1; @@ -347,30 +336,30 @@ mod tests { Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))), ); let test_states = &[ - (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON), - (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON), - (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON), - (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON), - (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON), - (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON), - (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON), - (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON), + (KEYCODE_ALT_LEFT, ModifierState::AltOn | ModifierState::AltLeftOn), + (KEYCODE_ALT_RIGHT, ModifierState::AltOn | ModifierState::AltRightOn), + (KEYCODE_CTRL_LEFT, ModifierState::CtrlOn | ModifierState::CtrlLeftOn), + (KEYCODE_CTRL_RIGHT, ModifierState::CtrlOn | ModifierState::CtrlRightOn), + (KEYCODE_SHIFT_LEFT, ModifierState::ShiftOn | ModifierState::ShiftLeftOn), + (KEYCODE_SHIFT_RIGHT, ModifierState::ShiftOn | ModifierState::ShiftRightOn), + (KEYCODE_META_LEFT, ModifierState::MetaOn | ModifierState::MetaLeftOn), + (KEYCODE_META_RIGHT, ModifierState::MetaOn | ModifierState::MetaRightOn), ]; for test_state in test_states.iter() { test_filter.clear(); test_callbacks.clear(); sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN }); - assert_eq!(test_callbacks.get_last_modifier_state(), 0); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP }); assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); // Re-send keys to lock it sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN }); assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP }); assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1); @@ -382,8 +371,8 @@ mod tests { assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1); sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP }); - assert_eq!(test_callbacks.get_last_modifier_state(), 0); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); } } @@ -398,14 +387,17 @@ mod tests { sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN }); sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP }); - assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!( + test_callbacks.get_last_modifier_state(), + ModifierState::CtrlLeftOn | ModifierState::CtrlOn + ); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }); sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }); - assert_eq!(test_callbacks.get_last_modifier_state(), 0); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); } #[test] @@ -427,20 +419,26 @@ mod tests { assert_eq!( test_callbacks.get_last_modifier_state(), - META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON + ModifierState::ShiftLeftOn + | ModifierState::ShiftOn + | ModifierState::CtrlLeftOn + | ModifierState::CtrlOn ); assert_eq!( test_callbacks.get_last_locked_modifier_state(), - META_CTRL_LEFT_ON | META_CTRL_ON + ModifierState::CtrlLeftOn | ModifierState::CtrlOn ); sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }); sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }); - assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON); + assert_eq!( + test_callbacks.get_last_modifier_state(), + ModifierState::CtrlLeftOn | ModifierState::CtrlOn + ); assert_eq!( test_callbacks.get_last_locked_modifier_state(), - META_CTRL_LEFT_ON | META_CTRL_ON + ModifierState::CtrlLeftOn | ModifierState::CtrlOn ); } @@ -458,13 +456,13 @@ mod tests { sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN }); assert_eq!( test_filter.last_event().unwrap().metaState as u32, - META_CTRL_LEFT_ON | META_CTRL_ON + (ModifierState::CtrlLeftOn | ModifierState::CtrlOn).bits() ); sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP }); assert_eq!( test_filter.last_event().unwrap().metaState as u32, - META_CTRL_LEFT_ON | META_CTRL_ON + (ModifierState::CtrlLeftOn | ModifierState::CtrlOn).bits() ); } @@ -499,15 +497,18 @@ mod tests { }); sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]); - assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON); + assert_eq!( + test_callbacks.get_last_modifier_state(), + ModifierState::CtrlLeftOn | ModifierState::CtrlOn + ); assert_eq!( test_callbacks.get_last_locked_modifier_state(), - META_CTRL_LEFT_ON | META_CTRL_ON + ModifierState::CtrlLeftOn | ModifierState::CtrlOn ); sticky_keys_filter.notify_devices_changed(&[]); - assert_eq!(test_callbacks.get_last_modifier_state(), 0); - assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0); + assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None); + assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None); } fn setup_filter( -- GitLab From ff517071a4b45ac6546bdf5770582881c183a286 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Wed, 5 Jun 2024 03:45:18 +0000 Subject: [PATCH 419/465] AndroidInputEvent: Migrate from TracePacket to WinscopeExtenstions Bug: 332714237 Test: atest inputflinger_tests Change-Id: I00dc546f6628ad8a33d0f6a6c1bbda10a24ed46e --- services/inputflinger/dispatcher/Android.bp | 5 +- .../trace/InputTracingPerfettoBackend.cpp | 14 +++- .../inputflinger/tests/InputTraceSession.cpp | 72 +++++++++++-------- .../inputflinger/tests/InputTraceSession.h | 1 - .../inputflinger/tests/InputTracingTest.cpp | 2 + 5 files changed, 59 insertions(+), 35 deletions(-) diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index 29aa3c3066..1a0ec48525 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -56,7 +56,9 @@ filegroup { cc_defaults { name: "libinputdispatcher_defaults", - srcs: [":libinputdispatcher_sources"], + srcs: [ + ":libinputdispatcher_sources", + ], shared_libs: [ "libbase", "libbinder", @@ -78,6 +80,7 @@ cc_defaults { "libattestation", "libgui_window_info_static", "libperfetto_client_experimental", + "perfetto_winscope_extensions_zero", ], target: { android: { diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 9b9633a1bd..3d30ad6c7b 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -229,7 +231,9 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* inputEvent = tracePacket->set_android_input_event(); + auto* winscopeExtensions = static_cast( + tracePacket->set_winscope_extensions()); + auto* inputEvent = winscopeExtensions->set_android_input_event(); auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted() : inputEvent->set_dispatcher_motion_event(); AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted); @@ -253,7 +257,9 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* inputEvent = tracePacket->set_android_input_event(); + auto* winscopeExtensions = static_cast( + tracePacket->set_winscope_extensions()); + auto* inputEvent = winscopeExtensions->set_android_input_event(); auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted() : inputEvent->set_dispatcher_key_event(); AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted); @@ -277,7 +283,9 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* inputEvent = tracePacket->set_android_input_event(); + auto* winscopeExtensions = static_cast( + tracePacket->set_winscope_extensions()); + auto* inputEvent = winscopeExtensions->set_android_input_event(); auto* dispatchEvent = isRedacted ? inputEvent->set_dispatcher_window_dispatch_event_redacted() : inputEvent->set_dispatcher_window_dispatch_event(); diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp index 32acb5f288..a9d370aedd 100644 --- a/services/inputflinger/tests/InputTraceSession.cpp +++ b/services/inputflinger/tests/InputTraceSession.cpp @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include @@ -30,6 +33,8 @@ using perfetto::protos::pbzero::AndroidInputEventConfig; using perfetto::protos::pbzero::AndroidKeyEvent; using perfetto::protos::pbzero::AndroidMotionEvent; using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent; +using perfetto::protos::pbzero::WinscopeExtensions; +using perfetto::protos::pbzero::WinscopeExtensionsImpl; // These operator<< definitions must be in the global namespace for them to be accessible to the // GTEST library. They cannot be in the anonymous namespace. @@ -85,38 +90,45 @@ auto decodeTrace(const std::string& rawTrace) { Trace::Decoder trace{rawTrace}; if (trace.has_packet()) { - auto it = trace.packet(); - while (it) { + for (auto it = trace.packet(); it; it++) { TracePacket::Decoder packet{it->as_bytes()}; - if (packet.has_android_input_event()) { - AndroidInputEvent::Decoder event{packet.android_input_event()}; - if (event.has_dispatcher_motion_event()) { - tracedMotions.emplace_back(event.dispatcher_motion_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_motion_event_redacted()) { - tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), - /*redacted=*/true); - } - if (event.has_dispatcher_key_event()) { - tracedKeys.emplace_back(event.dispatcher_key_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_key_event_redacted()) { - tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), - /*redacted=*/true); - } - if (event.has_dispatcher_window_dispatch_event()) { - tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_window_dispatch_event_redacted()) { - tracedWindowDispatches - .emplace_back(event.dispatcher_window_dispatch_event_redacted(), - /*redacted=*/true); - } + if (!packet.has_winscope_extensions()) { + continue; + } + + WinscopeExtensions::Decoder extensions{packet.winscope_extensions()}; + const auto& field = + extensions.Get(WinscopeExtensionsImpl::kAndroidInputEventFieldNumber); + if (!field.valid()) { + continue; + } + + AndroidInputEvent::Decoder event{field.as_bytes()}; + if (event.has_dispatcher_motion_event()) { + tracedMotions.emplace_back(event.dispatcher_motion_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_motion_event_redacted()) { + tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_key_event()) { + tracedKeys.emplace_back(event.dispatcher_key_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_key_event_redacted()) { + tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_window_dispatch_event()) { + tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_window_dispatch_event_redacted()) { + tracedWindowDispatches + .emplace_back(event.dispatcher_window_dispatch_event_redacted(), + /*redacted=*/true); } - it++; } } return std::tuple{std::move(tracedMotions), std::move(tracedKeys), diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h index ed20bc8343..bda552165e 100644 --- a/services/inputflinger/tests/InputTraceSession.h +++ b/services/inputflinger/tests/InputTraceSession.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp index 617d67f34d..2ccd93ec4c 100644 --- a/services/inputflinger/tests/InputTracingTest.cpp +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include -- GitLab From 9d3d561b46a49588162ad47c62ad01f7e37bc811 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Thu, 6 Jun 2024 20:34:26 +0000 Subject: [PATCH 420/465] InputDispatcher: Fix multi-display Pointer Capture There is an existing requirement that a window must both have focus and be on the focused display to be able to gain Pointer Capture. This means that focus changes on non-focused displays should not affect Pointer Capture, and that a window must lose capture if its display loses focus. Verify these requirements with a test. Bug: 342229227 Test: atest inputflinger_tests Change-Id: I7b1c73b7759d8f20436ee401ba657a5dc8ead7a5 --- .../dispatcher/InputDispatcher.cpp | 24 ++++++++------ .../tests/InputDispatcher_test.cpp | 31 +++++++++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 527edb6fe6..d22f3197bf 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5529,6 +5529,10 @@ void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) { } mFocusedDisplayId = displayId; + // Only a window on the focused display can have Pointer Capture, so disable the active + // Pointer Capture session if there is one, since the focused display changed. + disablePointerCaptureForcedLocked(); + // Find new focused window and validate sp newFocusedWindowToken = mFocusResolver.getFocusedWindowToken(displayId); sendFocusChangedCommandLocked(oldFocusedWindowToken, newFocusedWindowToken); @@ -6929,17 +6933,17 @@ void InputDispatcher::onFocusChangedLocked( enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason); } - // If a window has pointer capture, then it must have focus. We need to ensure that this - // contract is upheld when pointer capture is being disabled due to a loss of window focus. - // If the window loses focus before it loses pointer capture, then the window can be in a state - // where it has pointer capture but not focus, violating the contract. Therefore we must - // dispatch the pointer capture event before the focus event. Since focus events are added to - // the front of the queue (above), we add the pointer capture event to the front of the queue - // after the focus events are added. This ensures the pointer capture event ends up at the - // front. - disablePointerCaptureForcedLocked(); - if (mFocusedDisplayId == changes.displayId) { + // If a window has pointer capture, then it must have focus and must be on the top-focused + // display. We need to ensure that this contract is upheld when pointer capture is being + // disabled due to a loss of window focus. If the window loses focus before it loses pointer + // capture, then the window can be in a state where it has pointer capture but not focus, + // violating the contract. Therefore we must dispatch the pointer capture event before the + // focus event. Since focus events are added to the front of the queue (above), we add the + // pointer capture event to the front of the queue after the focus events are added. This + // ensures the pointer capture event ends up at the front. + disablePointerCaptureForcedLocked(); + sendFocusChangedCommandLocked(changes.oldFocus, changes.newFocus); } } diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 8de28c680f..780bc13f90 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -11054,6 +11054,37 @@ TEST_F(InputDispatcherPointerCaptureTests, MouseHoverAndPointerCapture) { mWindow->assertNoEvents(); } +TEST_F(InputDispatcherPointerCaptureTests, MultiDisplayPointerCapture) { + // The default display is the focused display to begin with. + requestAndVerifyPointerCapture(mWindow, true); + + // Move the second window to a second display, make it the focused window on that display. + mSecondWindow->editInfo()->displayId = SECOND_DISPLAY_ID; + mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0}); + setFocusedWindow(mSecondWindow); + mSecondWindow->consumeFocusEvent(true); + + mWindow->assertNoEvents(); + + // The second window cannot gain capture because it is not on the focused display. + mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true); + mFakePolicy->assertSetPointerCaptureNotCalled(); + mSecondWindow->assertNoEvents(); + + // Make the second display the focused display. + mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID); + + // This causes the first window to lose pointer capture, and it's unable to request capture. + mWindow->consumeCaptureEvent(false); + mFakePolicy->assertSetPointerCaptureCalled(mWindow, false); + + mDispatcher->requestPointerCapture(mWindow->getToken(), true); + mFakePolicy->assertSetPointerCaptureNotCalled(); + + // The second window is now able to gain pointer capture successfully. + requestAndVerifyPointerCapture(mSecondWindow, true); +} + using InputDispatcherPointerCaptureDeathTest = InputDispatcherPointerCaptureTests; TEST_F(InputDispatcherPointerCaptureDeathTest, -- GitLab From 8779d083464f7d104fbb128c174ef7694aa26c4b Mon Sep 17 00:00:00 2001 From: Priyanka Advani Date: Mon, 10 Jun 2024 19:07:02 +0000 Subject: [PATCH 421/465] Revert "AndroidInputEvent: Migrate from TracePacket to WinscopeE..." Revert submission 27683745-migrate-input-trace-winscope Reason for revert: Droidmonitor created revert due to b/346371417. Reverted changes: /q/submissionid:27683745-migrate-input-trace-winscope Change-Id: Ice87da2681a76b47d582c7ce957c598bb8c7fdb9 --- services/inputflinger/dispatcher/Android.bp | 5 +- .../trace/InputTracingPerfettoBackend.cpp | 14 +--- .../inputflinger/tests/InputTraceSession.cpp | 72 ++++++++----------- .../inputflinger/tests/InputTraceSession.h | 1 + .../inputflinger/tests/InputTracingTest.cpp | 2 - 5 files changed, 35 insertions(+), 59 deletions(-) diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index 1a0ec48525..29aa3c3066 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -56,9 +56,7 @@ filegroup { cc_defaults { name: "libinputdispatcher_defaults", - srcs: [ - ":libinputdispatcher_sources", - ], + srcs: [":libinputdispatcher_sources"], shared_libs: [ "libbase", "libbinder", @@ -80,7 +78,6 @@ cc_defaults { "libattestation", "libgui_window_info_static", "libperfetto_client_experimental", - "perfetto_winscope_extensions_zero", ], target: { android: { diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 3d30ad6c7b..9b9633a1bd 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -23,8 +23,6 @@ #include #include #include -#include -#include #include #include @@ -231,9 +229,7 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* winscopeExtensions = static_cast( - tracePacket->set_winscope_extensions()); - auto* inputEvent = winscopeExtensions->set_android_input_event(); + auto* inputEvent = tracePacket->set_android_input_event(); auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted() : inputEvent->set_dispatcher_motion_event(); AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted); @@ -257,9 +253,7 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* winscopeExtensions = static_cast( - tracePacket->set_winscope_extensions()); - auto* inputEvent = winscopeExtensions->set_android_input_event(); + auto* inputEvent = tracePacket->set_android_input_event(); auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted() : inputEvent->set_dispatcher_key_event(); AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted); @@ -283,9 +277,7 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* winscopeExtensions = static_cast( - tracePacket->set_winscope_extensions()); - auto* inputEvent = winscopeExtensions->set_android_input_event(); + auto* inputEvent = tracePacket->set_android_input_event(); auto* dispatchEvent = isRedacted ? inputEvent->set_dispatcher_window_dispatch_event_redacted() : inputEvent->set_dispatcher_window_dispatch_event(); diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp index a9d370aedd..32acb5f288 100644 --- a/services/inputflinger/tests/InputTraceSession.cpp +++ b/services/inputflinger/tests/InputTraceSession.cpp @@ -20,9 +20,6 @@ #include #include #include -#include -#include -#include #include @@ -33,8 +30,6 @@ using perfetto::protos::pbzero::AndroidInputEventConfig; using perfetto::protos::pbzero::AndroidKeyEvent; using perfetto::protos::pbzero::AndroidMotionEvent; using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent; -using perfetto::protos::pbzero::WinscopeExtensions; -using perfetto::protos::pbzero::WinscopeExtensionsImpl; // These operator<< definitions must be in the global namespace for them to be accessible to the // GTEST library. They cannot be in the anonymous namespace. @@ -90,45 +85,38 @@ auto decodeTrace(const std::string& rawTrace) { Trace::Decoder trace{rawTrace}; if (trace.has_packet()) { - for (auto it = trace.packet(); it; it++) { + auto it = trace.packet(); + while (it) { TracePacket::Decoder packet{it->as_bytes()}; - if (!packet.has_winscope_extensions()) { - continue; - } - - WinscopeExtensions::Decoder extensions{packet.winscope_extensions()}; - const auto& field = - extensions.Get(WinscopeExtensionsImpl::kAndroidInputEventFieldNumber); - if (!field.valid()) { - continue; - } - - AndroidInputEvent::Decoder event{field.as_bytes()}; - if (event.has_dispatcher_motion_event()) { - tracedMotions.emplace_back(event.dispatcher_motion_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_motion_event_redacted()) { - tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), - /*redacted=*/true); - } - if (event.has_dispatcher_key_event()) { - tracedKeys.emplace_back(event.dispatcher_key_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_key_event_redacted()) { - tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), - /*redacted=*/true); - } - if (event.has_dispatcher_window_dispatch_event()) { - tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_window_dispatch_event_redacted()) { - tracedWindowDispatches - .emplace_back(event.dispatcher_window_dispatch_event_redacted(), - /*redacted=*/true); + if (packet.has_android_input_event()) { + AndroidInputEvent::Decoder event{packet.android_input_event()}; + if (event.has_dispatcher_motion_event()) { + tracedMotions.emplace_back(event.dispatcher_motion_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_motion_event_redacted()) { + tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_key_event()) { + tracedKeys.emplace_back(event.dispatcher_key_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_key_event_redacted()) { + tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_window_dispatch_event()) { + tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_window_dispatch_event_redacted()) { + tracedWindowDispatches + .emplace_back(event.dispatcher_window_dispatch_event_redacted(), + /*redacted=*/true); + } } + it++; } } return std::tuple{std::move(tracedMotions), std::move(tracedKeys), diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h index bda552165e..ed20bc8343 100644 --- a/services/inputflinger/tests/InputTraceSession.h +++ b/services/inputflinger/tests/InputTraceSession.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp index 2ccd93ec4c..617d67f34d 100644 --- a/services/inputflinger/tests/InputTracingTest.cpp +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -30,8 +30,6 @@ #include #include #include -#include -#include #include #include #include -- GitLab From 9a53b55af5228baef78a965d191dec83310009dc Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 4 Jun 2024 02:59:40 +0000 Subject: [PATCH 422/465] MotionEvent: Differentiate directional support for AXIS_ORIENTATION We have three cases for handling AXIS_ORIENTATION: 1. Orientation is not supported by the input device, so the value for AXIS_ORIENTATION should always be 0, regardless of display rotation. 2. Orientation is supported, but a "direction" is not specified, like for touchscreens and touchpads. The orientation must be in the range [-pi/2, pi/2] for all display rotations. 3. Orientation is fully supported, and the value is in the range [-pi, pi] for all display rotations. It is insufficient to rely on whether or not the PointerCoords has the bit for AXIS_ORIENTATION set to determine whether the event has a valid orientation. This is because we always skip setting values of 0 for any axis in PointerCoords to save space during serialization. To support these three cases, we introduce two new MotionEvent private flags. These are flags that are not exposed to Java and to the public APIs. Bug: 263310669 Test: atest TouchScreenTest libinput_tests inputflinger_tests Change-Id: Iaa38afe35b00de74fbc5eefce25191bea52c2ea6 --- include/input/Input.h | 28 +++- libs/input/Input.cpp | 43 ++++-- libs/input/android/os/IInputConstants.aidl | 25 ++++ libs/input/tests/InputEvent_test.cpp | 139 ++++++++++++++++-- ...tPublisherAndConsumerNoResampling_test.cpp | 4 +- .../tests/InputPublisherAndConsumer_test.cpp | 4 +- .../dispatcher/InputDispatcher.cpp | 8 +- .../trace/AndroidInputEventProtoConverter.cpp | 3 +- .../reader/mapper/TouchInputMapper.cpp | 17 ++- .../inputflinger/tests/InputReader_test.cpp | 4 + 10 files changed, 232 insertions(+), 43 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index 3ca9c19876..a96dae2510 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -99,6 +99,18 @@ enum { /* Motion event is inconsistent with previously sent motion events. */ AMOTION_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED, + + /** Private flag, not used in Java. */ + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION = + android::os::IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION, + + /** Private flag, not used in Java. */ + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = android::os::IInputConstants:: + MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION, + + /** Mask for all private flags that are not used in Java. */ + AMOTION_EVENT_PRIVATE_FLAG_MASK = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION, }; /** @@ -209,8 +221,12 @@ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) * Transform an angle on the x-y plane. An angle of 0 radians corresponds to "north" or * pointing upwards in the negative Y direction, a positive angle points towards the right, and a * negative angle points towards the left. + * + * If the angle represents a direction that needs to be preserved, set isDirectional to true to get + * an output range of [-pi, pi]. If the angle's direction does not need to be preserved, set + * isDirectional to false to get an output range of [-pi/2, pi/2]. */ -float transformAngle(const ui::Transform& transform, float angleRadians); +float transformAngle(const ui::Transform& transform, float angleRadians, bool isDirectional); /** * The type of the InputEvent. @@ -462,7 +478,7 @@ struct PointerCoords { // axes, however the window scaling will not. void scale(float globalScale, float windowXScale, float windowYScale); - void transform(const ui::Transform& transform); + void transform(const ui::Transform& transform, int32_t motionEventFlags); inline float getX() const { return getAxisValue(AMOTION_EVENT_AXIS_X); @@ -930,10 +946,10 @@ public: // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods // are used to apply these transformations for different axes. static vec2 calculateTransformedXY(uint32_t source, const ui::Transform&, const vec2& xy); - static float calculateTransformedAxisValue(int32_t axis, uint32_t source, const ui::Transform&, - const PointerCoords&); - static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&, - const PointerCoords&); + static float calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags, + const ui::Transform&, const PointerCoords&); + static PointerCoords calculateTransformedCoords(uint32_t source, int32_t flags, + const ui::Transform&, const PointerCoords&); // The rounding precision for transformed motion events. static constexpr float ROUNDING_PRECISION = 0.001f; diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index ee121d53fe..0a3f1fd463 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -96,6 +96,19 @@ int32_t resolveActionForSplitMotionEvent( return AMOTION_EVENT_ACTION_DOWN; } +float transformOrientation(const ui::Transform& transform, const PointerCoords& coords, + int32_t motionEventFlags) { + if ((motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION) == 0) { + return 0; + } + + const bool isDirectionalAngle = + (motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION) != 0; + + return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), + isDirectionalAngle); +} + } // namespace const char* motionClassificationToString(MotionClassification classification) { @@ -187,7 +200,7 @@ vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) return roundTransformedCoords(transformedXy - transformedOrigin); } -float transformAngle(const ui::Transform& transform, float angleRadians) { +float transformAngle(const ui::Transform& transform, float angleRadians, bool isDirectional) { // Construct and transform a vector oriented at the specified clockwise angle from vertical. // Coordinate system: down is increasing Y, right is increasing X. float x = sinf(angleRadians); @@ -201,6 +214,11 @@ float transformAngle(const ui::Transform& transform, float angleRadians) { transformedPoint.x -= origin.x; transformedPoint.y -= origin.y; + if (!isDirectional && transformedPoint.y > 0) { + // Limit the range of atan2f to [-pi/2, pi/2] by reversing the direction of the vector. + transformedPoint *= -1; + } + // Derive the transformed vector's clockwise angle from vertical. // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API. return atan2f(transformedPoint.x, -transformedPoint.y); @@ -530,7 +548,7 @@ bool PointerCoords::operator==(const PointerCoords& other) const { return true; } -void PointerCoords::transform(const ui::Transform& transform) { +void PointerCoords::transform(const ui::Transform& transform, int32_t motionEventFlags) { const vec2 xy = transform.transform(getXYValue()); setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); @@ -544,9 +562,9 @@ void PointerCoords::transform(const ui::Transform& transform) { setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y); } - if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_ORIENTATION)) { - const float val = getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); - setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(transform, val)); + if ((motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION) != 0) { + setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, + transformOrientation(transform, *this, motionEventFlags)); } } @@ -723,13 +741,13 @@ const PointerCoords* MotionEvent::getHistoricalRawPointerCoords( float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex, size_t historicalIndex) const { const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex); - return calculateTransformedAxisValue(axis, mSource, mRawTransform, coords); + return calculateTransformedAxisValue(axis, mSource, mFlags, mRawTransform, coords); } float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex, size_t historicalIndex) const { const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex); - return calculateTransformedAxisValue(axis, mSource, mTransform, coords); + return calculateTransformedAxisValue(axis, mSource, mFlags, mTransform, coords); } ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const { @@ -787,7 +805,7 @@ void MotionEvent::applyTransform(const std::array& matrix) { // Apply the transformation to all samples. std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(), - [&transform](PointerCoords& c) { c.transform(transform); }); + [&](PointerCoords& c) { c.transform(transform, mFlags); }); if (mRawXCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION && mRawYCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION) { @@ -1059,7 +1077,7 @@ vec2 MotionEvent::calculateTransformedXY(uint32_t source, const ui::Transform& t } // Keep in sync with calculateTransformedCoords. -float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, +float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags, const ui::Transform& transform, const PointerCoords& coords) { if (shouldDisregardTransformation(source)) { @@ -1081,7 +1099,7 @@ float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, } if (axis == AMOTION_EVENT_AXIS_ORIENTATION) { - return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + return transformOrientation(transform, coords, flags); } return coords.getAxisValue(axis); @@ -1089,7 +1107,7 @@ float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, // Keep in sync with calculateTransformedAxisValue. This is an optimization of // calculateTransformedAxisValue for all PointerCoords axes. -PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, +PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, int32_t flags, const ui::Transform& transform, const PointerCoords& coords) { if (shouldDisregardTransformation(source)) { @@ -1109,8 +1127,7 @@ PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y); out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, - transformAngle(transform, - coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION))); + transformOrientation(transform, coords, flags)); return out; } diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl index 90ed2b7d06..650dc5c677 100644 --- a/libs/input/android/os/IInputConstants.aidl +++ b/libs/input/android/os/IInputConstants.aidl @@ -115,6 +115,31 @@ interface IInputConstants */ const int MOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40; + /** + * This flag indicates that the event has a valid value for AXIS_ORIENTATION. + * + * This is a private flag that is not used in Java. + * @hide + */ + const int MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80; + + /** + * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine + * the direction in which the tool is pointing. The value of the orientation axis will be in + * the range [-pi, pi], which represents a full circle. This is usually supported by devices + * like styluses. + * + * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing + * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2], + * which represents half a circle. This is usually the case for devices like touchscreens and + * touchpads, for which it is difficult to tell which direction along the major axis of the + * touch ellipse the finger is pointing. + * + * This is a private flag that is not used in Java. + * @hide + */ + const int MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100; + /** * The input event was generated or modified by accessibility service. * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp index 476b5cf818..3717f49fef 100644 --- a/libs/input/tests/InputEvent_test.cpp +++ b/libs/input/tests/InputEvent_test.cpp @@ -26,23 +26,39 @@ namespace android { +namespace { + // Default display id. -static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; +constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; -static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; +constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION; -static constexpr auto POINTER_0_DOWN = +constexpr auto POINTER_0_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -static constexpr auto POINTER_1_DOWN = +constexpr auto POINTER_1_DOWN = AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -static constexpr auto POINTER_0_UP = +constexpr auto POINTER_0_UP = AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); -static constexpr auto POINTER_1_UP = +constexpr auto POINTER_1_UP = AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); +std::array asFloat9(const ui::Transform& t) { + std::array mat{}; + mat[0] = t[0][0]; + mat[1] = t[1][0]; + mat[2] = t[2][0]; + mat[3] = t[0][1]; + mat[4] = t[1][1]; + mat[5] = t[2][1]; + mat[6] = t[0][2]; + mat[7] = t[1][2]; + mat[8] = t[2][2]; + return mat; +} + class BaseTest : public testing::Test { protected: static constexpr std::array HMAC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, @@ -50,6 +66,8 @@ protected: 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; }; +} // namespace + // --- PointerCoordsTest --- class PointerCoordsTest : public BaseTest { @@ -344,13 +362,15 @@ void MotionEventTest::SetUp() { } void MotionEventTest::initializeEventWithHistory(MotionEvent* event) { + const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION; event->initialize(mId, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC, - AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, - AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, - MotionClassification::NONE, mTransform, 2.0f, 2.1f, - AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, - mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2, - mPointerProperties, mSamples[0].pointerCoords); + AMOTION_EVENT_ACTION_MOVE, 0, flags, AMOTION_EVENT_EDGE_FLAG_TOP, + AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, MotionClassification::NONE, + mTransform, 2.0f, 2.1f, AMOTION_EVENT_INVALID_CURSOR_POSITION, + AMOTION_EVENT_INVALID_CURSOR_POSITION, mRawTransform, ARBITRARY_DOWN_TIME, + ARBITRARY_EVENT_TIME, 2, mPointerProperties, mSamples[0].pointerCoords); event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords); event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords); } @@ -364,7 +384,10 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { ASSERT_EQ(DISPLAY_ID, event->getDisplayId()); EXPECT_EQ(HMAC, event->getHmac()); ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, event->getAction()); - ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, event->getFlags()); + ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION, + event->getFlags()); ASSERT_EQ(AMOTION_EVENT_EDGE_FLAG_TOP, event->getEdgeFlags()); ASSERT_EQ(AMETA_ALT_ON, event->getMetaState()); ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState()); @@ -799,8 +822,10 @@ TEST_F(MotionEventTest, Transform) { } MotionEvent event; ui::Transform identityTransform; + const int32_t flags = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION; event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, - INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, /*flags=*/0, + INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, flags, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform, /*xPrecision=*/0, /*yPrecision=*/0, /*xCursorPosition=*/3 + RADIUS, /*yCursorPosition=*/2, @@ -1087,4 +1112,90 @@ TEST_F(MotionEventTest, CoordinatesAreRoundedAppropriately) { ASSERT_EQ(EXPECTED.y, event.getYCursorPosition()); } +TEST_F(MotionEventTest, InvalidOrientationNotRotated) { + // This touch event does not have a value for AXIS_ORIENTATION, and the flags are implicitly + // set to 0. The transform is set to a 90-degree rotation. + MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4)) + .transform(ui::Transform(ui::Transform::ROT_90, 100, 100)) + .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50)) + .build(); + ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f); + event.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100))); + ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f); + event.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100))); + ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f); + event.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100))); + ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f); +} + +TEST_F(MotionEventTest, ValidZeroOrientationRotated) { + // This touch events will implicitly have a value of 0 for its AXIS_ORIENTATION. + auto builder = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4)) + .transform(ui::Transform(ui::Transform::ROT_90, 100, 100)) + .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50)) + .addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION); + MotionEvent nonDirectionalEvent = builder.build(); + MotionEvent directionalEvent = + builder.addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION).build(); + + // The angle is rotated by the initial transform, a 90-degree rotation. + ASSERT_NEAR(fabs(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI_2, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), M_PI_2, EPSILON); + + nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100))); + directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100))); + ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON); + ASSERT_NEAR(fabs(directionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI, EPSILON); + + nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100))); + directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100))); + ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON); + + nonDirectionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100))); + directionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100))); + ASSERT_NEAR(fabs(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI_2, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), -M_PI_2, EPSILON); +} + +TEST_F(MotionEventTest, ValidNonZeroOrientationRotated) { + const float initial = 1.f; + auto builder = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .downTime(ARBITRARY_DOWN_TIME) + .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER) + .x(4) + .y(4) + .axis(AMOTION_EVENT_AXIS_ORIENTATION, initial)) + .transform(ui::Transform(ui::Transform::ROT_90, 100, 100)) + .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50)) + .addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION); + + MotionEvent nonDirectionalEvent = builder.build(); + MotionEvent directionalEvent = + builder.addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION).build(); + + // The angle is rotated by the initial transform, a 90-degree rotation. + ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial + M_PI_2, EPSILON); + + nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100))); + directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100))); + ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI, EPSILON); + + nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100))); + directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100))); + ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON); + + nonDirectionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100))); + directionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100))); + ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON); + ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON); +} + } // namespace android diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp index 70529bbd39..f49469ccca 100644 --- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp @@ -96,7 +96,9 @@ PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime, hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION; if (action == AMOTION_EVENT_ACTION_CANCEL) { flags |= AMOTION_EVENT_FLAG_CANCELED; } diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp index 48512f7c6e..e65a919bd6 100644 --- a/libs/input/tests/InputPublisherAndConsumer_test.cpp +++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp @@ -89,7 +89,9 @@ PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime, hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}; - flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; + flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION; if (action == AMOTION_EVENT_ACTION_CANCEL) { flags |= AMOTION_EVENT_FLAG_CANCELED; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 527edb6fe6..8b38874afa 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -444,10 +444,10 @@ std::unique_ptr createDispatchEntry(const IdGenerator& idGenerato newCoords.copyFrom(motionEntry.pointerCoords[i]); // First, apply the current pointer's transform to update the coordinates into // window space. - newCoords.transform(currTransform); + newCoords.transform(currTransform, motionEntry.flags); // Next, apply the inverse transform of the normalized coordinates so the // current coordinates are transformed into the normalized coordinate space. - newCoords.transform(inverseTransform); + newCoords.transform(inverseTransform, motionEntry.flags); } } @@ -5133,8 +5133,8 @@ void InputDispatcher::transformMotionEntryForInjectionLocked( } for (uint32_t i = 0; i < entry.getPointerCount(); i++) { entry.pointerCoords[i] = - MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay, - entry.pointerCoords[i]); + MotionEvent::calculateTransformedCoords(entry.source, entry.flags, + transformToDisplay, entry.pointerCoords[i]); } } diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp index 2d7554c9c1..0b17507c2c 100644 --- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp +++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp @@ -123,7 +123,8 @@ void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent( const auto& coords = motion->pointerCoords[i]; const auto coordsInWindow = - MotionEvent::calculateTransformedCoords(motion->source, args.transform, coords); + MotionEvent::calculateTransformedCoords(motion->source, motion->flags, + args.transform, coords); auto bits = BitSet64(coords.bits); for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) { const uint32_t axis = bits.clearFirstMarkedBit(); diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp index 9d049ae780..a3834908a7 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp +++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp @@ -2343,20 +2343,23 @@ void TouchInputMapper::cookPointerData() { if (mHaveTilt) { float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale; float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale; - orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle))); + orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)), + /*isDirectional=*/true); tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle)); } else { tilt = 0; switch (mCalibration.orientationCalibration) { case Calibration::OrientationCalibration::INTERPOLATED: - orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale); + orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale, + /*isDirectional=*/true); break; case Calibration::OrientationCalibration::VECTOR: { int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); int32_t c2 = signExtendNybble(in.orientation & 0x0f); if (c1 != 0 || c2 != 0) { - orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f); + orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f, + /*isDirectional=*/true); float confidence = hypotf(c1, c2); float scale = 1.0f + confidence / 16.0f; touchMajor *= scale; @@ -3672,6 +3675,14 @@ NotifyMotionArgs TouchInputMapper::dispatchMotion( if (mCurrentStreamModifiedByExternalStylus) { source |= AINPUT_SOURCE_BLUETOOTH_STYLUS; } + if (mOrientedRanges.orientation.has_value()) { + flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION; + if (mOrientedRanges.tilt.has_value()) { + // In the current implementation, only devices that report a value for tilt supports + // directional orientation. + flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION; + } + } const ui::LogicalDisplayId displayId = getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID); diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index 2e65d4a48e..804b4f77e6 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -5485,6 +5485,9 @@ TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) { ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], x, y, pressure, size, tool, tool, tool, tool, orientation, distance)); ASSERT_EQ(tilt, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TILT)); + ASSERT_EQ(args.flags, + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION | + AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION); } TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) { @@ -7927,6 +7930,7 @@ TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) { ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, orientation, distance)); + ASSERT_EQ(args.flags, AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION); } TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) { -- GitLab From 4b8d36c0e1cbeaee7b23a3a1dd65eddb0fae11fa Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 7 Jun 2024 15:10:46 +0000 Subject: [PATCH 423/465] MotionEvent: Consolidate functions to transform PointerCoords Remove PointerCoords::transform(), which is a duplicate of MotionEvent::calculateTranformedCoords(). Bug: 342349872 Test: atest inputflinger_tests libinput_tests Change-Id: I7610a0475a16e9964817f63efa67b291cb7aaf0a --- include/input/Input.h | 4 +- libs/input/Input.cpp | 51 +++++++------------ .../dispatcher/InputDispatcher.cpp | 6 ++- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/include/input/Input.h b/include/input/Input.h index a96dae2510..3e7a6fd1c5 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -478,8 +478,6 @@ struct PointerCoords { // axes, however the window scaling will not. void scale(float globalScale, float windowXScale, float windowYScale); - void transform(const ui::Transform& transform, int32_t motionEventFlags); - inline float getX() const { return getAxisValue(AMOTION_EVENT_AXIS_X); } @@ -948,6 +946,8 @@ public: static vec2 calculateTransformedXY(uint32_t source, const ui::Transform&, const vec2& xy); static float calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags, const ui::Transform&, const PointerCoords&); + static void calculateTransformedCoordsInPlace(PointerCoords& coords, uint32_t source, + int32_t flags, const ui::Transform&); static PointerCoords calculateTransformedCoords(uint32_t source, int32_t flags, const ui::Transform&, const PointerCoords&); // The rounding precision for transformed motion events. diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp index 0a3f1fd463..b09814797f 100644 --- a/libs/input/Input.cpp +++ b/libs/input/Input.cpp @@ -548,26 +548,6 @@ bool PointerCoords::operator==(const PointerCoords& other) const { return true; } -void PointerCoords::transform(const ui::Transform& transform, int32_t motionEventFlags) { - const vec2 xy = transform.transform(getXYValue()); - setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); - setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); - - if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_RELATIVE_X) || - BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_RELATIVE_Y)) { - const ui::Transform rotation(transform.getOrientation()); - const vec2 relativeXy = rotation.transform(getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), - getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)); - setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x); - setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y); - } - - if ((motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION) != 0) { - setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, - transformOrientation(transform, *this, motionEventFlags)); - } -} - // --- MotionEvent --- void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, @@ -804,8 +784,9 @@ void MotionEvent::applyTransform(const std::array& matrix) { transform.set(matrix); // Apply the transformation to all samples. - std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(), - [&](PointerCoords& c) { c.transform(transform, mFlags); }); + std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(), [&](PointerCoords& c) { + calculateTransformedCoordsInPlace(c, mSource, mFlags, transform); + }); if (mRawXCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION && mRawYCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION) { @@ -1107,28 +1088,32 @@ float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, // Keep in sync with calculateTransformedAxisValue. This is an optimization of // calculateTransformedAxisValue for all PointerCoords axes. -PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, int32_t flags, - const ui::Transform& transform, - const PointerCoords& coords) { +void MotionEvent::calculateTransformedCoordsInPlace(PointerCoords& coords, uint32_t source, + int32_t flags, const ui::Transform& transform) { if (shouldDisregardTransformation(source)) { - return coords; + return; } - PointerCoords out = coords; const vec2 xy = calculateTransformedXYUnchecked(source, transform, coords.getXYValue()); - out.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); - out.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); + coords.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); + coords.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); const vec2 relativeXy = transformWithoutTranslation(transform, {coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X), coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)}); - out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x); - out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x); + coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y); - out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, - transformOrientation(transform, coords, flags)); + coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, + transformOrientation(transform, coords, flags)); +} +PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, int32_t flags, + const ui::Transform& transform, + const PointerCoords& coords) { + PointerCoords out = coords; + calculateTransformedCoordsInPlace(out, source, flags, transform); return out; } diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 8b38874afa..96d9dc1e1a 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -444,10 +444,12 @@ std::unique_ptr createDispatchEntry(const IdGenerator& idGenerato newCoords.copyFrom(motionEntry.pointerCoords[i]); // First, apply the current pointer's transform to update the coordinates into // window space. - newCoords.transform(currTransform, motionEntry.flags); + MotionEvent::calculateTransformedCoordsInPlace(newCoords, motionEntry.source, + motionEntry.flags, currTransform); // Next, apply the inverse transform of the normalized coordinates so the // current coordinates are transformed into the normalized coordinate space. - newCoords.transform(inverseTransform, motionEntry.flags); + MotionEvent::calculateTransformedCoordsInPlace(newCoords, motionEntry.source, + motionEntry.flags, inverseTransform); } } -- GitLab From 6ef947adb43edb61ae81e7a04b47ae100f3fde1f Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 10 Jun 2024 20:00:51 +0000 Subject: [PATCH 424/465] Revert "Revert "AndroidInputEvent: Migrate from TracePacket to W..." Re-land "AndroidInputEvent: Migrate from TracePacket to WinscopeE..." Reason for revert: Second attempt. Topic was split in the first attempt, causing build failures. Reverted changes: /q/submissionid:27769399-revert-27683745-migrate-input-trace-winscope-OTCESQVQHW Bug: 332714237 Change-Id: Ia5b8ec30023462806bcc67421c8257e436c8e8c4 --- services/inputflinger/dispatcher/Android.bp | 5 +- .../trace/InputTracingPerfettoBackend.cpp | 14 +++- .../inputflinger/tests/InputTraceSession.cpp | 72 +++++++++++-------- .../inputflinger/tests/InputTraceSession.h | 1 - .../inputflinger/tests/InputTracingTest.cpp | 2 + 5 files changed, 59 insertions(+), 35 deletions(-) diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp index 29aa3c3066..1a0ec48525 100644 --- a/services/inputflinger/dispatcher/Android.bp +++ b/services/inputflinger/dispatcher/Android.bp @@ -56,7 +56,9 @@ filegroup { cc_defaults { name: "libinputdispatcher_defaults", - srcs: [":libinputdispatcher_sources"], + srcs: [ + ":libinputdispatcher_sources", + ], shared_libs: [ "libbase", "libbinder", @@ -78,6 +80,7 @@ cc_defaults { "libattestation", "libgui_window_info_static", "libperfetto_client_experimental", + "perfetto_winscope_extensions_zero", ], target: { android: { diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 9b9633a1bd..3d30ad6c7b 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -229,7 +231,9 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* inputEvent = tracePacket->set_android_input_event(); + auto* winscopeExtensions = static_cast( + tracePacket->set_winscope_extensions()); + auto* inputEvent = winscopeExtensions->set_android_input_event(); auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted() : inputEvent->set_dispatcher_motion_event(); AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted); @@ -253,7 +257,9 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* inputEvent = tracePacket->set_android_input_event(); + auto* winscopeExtensions = static_cast( + tracePacket->set_winscope_extensions()); + auto* inputEvent = winscopeExtensions->set_android_input_event(); auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted() : inputEvent->set_dispatcher_key_event(); AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted); @@ -277,7 +283,9 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); - auto* inputEvent = tracePacket->set_android_input_event(); + auto* winscopeExtensions = static_cast( + tracePacket->set_winscope_extensions()); + auto* inputEvent = winscopeExtensions->set_android_input_event(); auto* dispatchEvent = isRedacted ? inputEvent->set_dispatcher_window_dispatch_event_redacted() : inputEvent->set_dispatcher_window_dispatch_event(); diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp index 32acb5f288..a9d370aedd 100644 --- a/services/inputflinger/tests/InputTraceSession.cpp +++ b/services/inputflinger/tests/InputTraceSession.cpp @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include @@ -30,6 +33,8 @@ using perfetto::protos::pbzero::AndroidInputEventConfig; using perfetto::protos::pbzero::AndroidKeyEvent; using perfetto::protos::pbzero::AndroidMotionEvent; using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent; +using perfetto::protos::pbzero::WinscopeExtensions; +using perfetto::protos::pbzero::WinscopeExtensionsImpl; // These operator<< definitions must be in the global namespace for them to be accessible to the // GTEST library. They cannot be in the anonymous namespace. @@ -85,38 +90,45 @@ auto decodeTrace(const std::string& rawTrace) { Trace::Decoder trace{rawTrace}; if (trace.has_packet()) { - auto it = trace.packet(); - while (it) { + for (auto it = trace.packet(); it; it++) { TracePacket::Decoder packet{it->as_bytes()}; - if (packet.has_android_input_event()) { - AndroidInputEvent::Decoder event{packet.android_input_event()}; - if (event.has_dispatcher_motion_event()) { - tracedMotions.emplace_back(event.dispatcher_motion_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_motion_event_redacted()) { - tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), - /*redacted=*/true); - } - if (event.has_dispatcher_key_event()) { - tracedKeys.emplace_back(event.dispatcher_key_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_key_event_redacted()) { - tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), - /*redacted=*/true); - } - if (event.has_dispatcher_window_dispatch_event()) { - tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), - /*redacted=*/false); - } - if (event.has_dispatcher_window_dispatch_event_redacted()) { - tracedWindowDispatches - .emplace_back(event.dispatcher_window_dispatch_event_redacted(), - /*redacted=*/true); - } + if (!packet.has_winscope_extensions()) { + continue; + } + + WinscopeExtensions::Decoder extensions{packet.winscope_extensions()}; + const auto& field = + extensions.Get(WinscopeExtensionsImpl::kAndroidInputEventFieldNumber); + if (!field.valid()) { + continue; + } + + AndroidInputEvent::Decoder event{field.as_bytes()}; + if (event.has_dispatcher_motion_event()) { + tracedMotions.emplace_back(event.dispatcher_motion_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_motion_event_redacted()) { + tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_key_event()) { + tracedKeys.emplace_back(event.dispatcher_key_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_key_event_redacted()) { + tracedKeys.emplace_back(event.dispatcher_key_event_redacted(), + /*redacted=*/true); + } + if (event.has_dispatcher_window_dispatch_event()) { + tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(), + /*redacted=*/false); + } + if (event.has_dispatcher_window_dispatch_event_redacted()) { + tracedWindowDispatches + .emplace_back(event.dispatcher_window_dispatch_event_redacted(), + /*redacted=*/true); } - it++; } } return std::tuple{std::move(tracedMotions), std::move(tracedKeys), diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h index ed20bc8343..bda552165e 100644 --- a/services/inputflinger/tests/InputTraceSession.h +++ b/services/inputflinger/tests/InputTraceSession.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp index 617d67f34d..2ccd93ec4c 100644 --- a/services/inputflinger/tests/InputTracingTest.cpp +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include -- GitLab From 1f39c3d3ceaa140c480acbb23504490c2f49450e Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 30 May 2024 21:01:26 -0400 Subject: [PATCH 425/465] FTL: Add FTL_EXPECT Like FTL_TRY, FTL_EXPECT unwraps T for Expected or does an early out on error, but it bails out with E instead of Expected. Fix a line in the Expected.Try test to call the intended helper. Bug: 185536303 Test: ftl_test Change-Id: I238ae2978ff606c5c035e9791496c2b20729ca7f --- include/ftl/expected.h | 30 +++++++++++++++++++++++++++++ libs/ftl/expected_test.cpp | 39 ++++++++++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/include/ftl/expected.h b/include/ftl/expected.h index 57448dc1e0..7e765c5681 100644 --- a/include/ftl/expected.h +++ b/include/ftl/expected.h @@ -69,6 +69,36 @@ exp_.value(); \ }) +// Given an expression `expr` that evaluates to an ftl::Expected result (R for short), +// FTL_EXPECT unwraps T out of R, or bails out of the enclosing function F if R has an error E. +// While FTL_TRY bails out with R, FTL_EXPECT bails out with E, which is useful when F does not +// need to propagate R because T is not relevant to the caller. +// +// Example usage: +// +// using StringExp = ftl::Expected; +// +// std::errc repeat(StringExp exp, std::string& out) { +// const std::string str = FTL_EXPECT(exp); +// out = str + str; +// return std::errc::operation_in_progress; +// } +// +// std::string str; +// assert(std::errc::operation_in_progress == repeat(StringExp("ha"s), str)); +// assert("haha"s == str); +// assert(std::errc::bad_message == repeat(ftl::Unexpected(std::errc::bad_message), str)); +// assert("haha"s == str); +// +#define FTL_EXPECT(expr) \ + ({ \ + auto exp_ = (expr); \ + if (!exp_.has_value()) { \ + return std::move(exp_.error()); \ + } \ + exp_.value(); \ + }) + namespace android::ftl { // Superset of base::expected with monadic operations. diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp index 9b7f017df3..d5b1d7ea8f 100644 --- a/libs/ftl/expected_test.cpp +++ b/libs/ftl/expected_test.cpp @@ -79,16 +79,28 @@ TEST(Expected, ValueOpt) { namespace { -IntExp increment(IntExp exp) { +IntExp increment_try(IntExp exp) { const int i = FTL_TRY(exp); return IntExp(i + 1); } -StringExp repeat(StringExp exp) { +std::errc increment_expect(IntExp exp, int& out) { + const int i = FTL_EXPECT(exp); + out = i + 1; + return std::errc::operation_in_progress; +} + +StringExp repeat_try(StringExp exp) { const std::string str = FTL_TRY(exp); return StringExp(str + str); } +std::errc repeat_expect(StringExp exp, std::string& out) { + const std::string str = FTL_EXPECT(exp); + out = str + str; + return std::errc::operation_in_progress; +} + void uppercase(char& c, ftl::Optional opt) { c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit()))); } @@ -97,13 +109,13 @@ void uppercase(char& c, ftl::Optional opt) { // Keep in sync with example usage in header file. TEST(Expected, Try) { - EXPECT_EQ(IntExp(100), increment(IntExp(99))); - EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) { + EXPECT_EQ(IntExp(100), increment_try(IntExp(99))); + EXPECT_TRUE(increment_try(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) { return e == std::errc::value_too_large; })); - EXPECT_EQ(StringExp("haha"s), repeat(StringExp("ha"s))); - EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) { + EXPECT_EQ(StringExp("haha"s), repeat_try(StringExp("ha"s))); + EXPECT_TRUE(repeat_try(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) { return e == std::errc::bad_message; })); @@ -115,4 +127,19 @@ TEST(Expected, Try) { EXPECT_EQ(c, 'A'); } +TEST(Expected, Expect) { + int i = 0; + EXPECT_EQ(std::errc::operation_in_progress, increment_expect(IntExp(99), i)); + EXPECT_EQ(100, i); + EXPECT_EQ(std::errc::value_too_large, + increment_expect(ftl::Unexpected(std::errc::value_too_large), i)); + EXPECT_EQ(100, i); + + std::string str; + EXPECT_EQ(std::errc::operation_in_progress, repeat_expect(StringExp("ha"s), str)); + EXPECT_EQ("haha"s, str); + EXPECT_EQ(std::errc::bad_message, repeat_expect(ftl::Unexpected(std::errc::bad_message), str)); + EXPECT_EQ("haha"s, str); +} + } // namespace android::test -- GitLab From 021ab8c27e28f3d4c891c117db8f220e7f5ec74f Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Mon, 10 Jun 2024 12:32:34 -0400 Subject: [PATCH 426/465] FTL: Amend outdated ftl::Function example usage Bug: 185536303 Flag: DOCS_ONLY Test: N/A Change-Id: I96246cacb63dec70caa732064fd5159d596d4e25 --- include/ftl/function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ftl/function.h b/include/ftl/function.h index 3538ca4eae..bda5b759bb 100644 --- a/include/ftl/function.h +++ b/include/ftl/function.h @@ -123,7 +123,7 @@ namespace android::ftl { // // Create a typedef to give a more meaningful name and bound the size. // using MyFunction = ftl::Function; // int* ptr = nullptr; -// auto f1 = MyFunction::make_function( +// auto f1 = MyFunction::make( // [cls = &cls, ptr](std::string_view sv) { // return cls->on_string(ptr, sv); // }); -- GitLab From cda32662d9784812d767c657a1e573b331fe77a0 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Jun 2024 15:31:40 +0000 Subject: [PATCH 427/465] Add new MotionEvent orientation flags to rust Bug: 263310669 Bug: 346599999 Test: manual Change-Id: I902faf586ad498c0ba8111dd0f89a788ca353866 --- libs/input/rust/input.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index 0574245fa6..564d94dbb4 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -207,6 +207,11 @@ bitflags! { const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32; /// FLAG_NO_FOCUS_CHANGE const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32; + /// PRIVATE_FLAG_SUPPORTS_ORIENTATION + const PRIVATE_SUPPORTS_ORIENTATION = IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION as u32; + /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION + const PRIVATE_SUPPORTS_DIRECTIONAL_ORIENTATION = + IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION as u32; /// FLAG_IS_ACCESSIBILITY_EVENT const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32; /// FLAG_TAINTED -- GitLab From 7cf348d281393d2b529da79ad2bd436ade6ec9ed Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Jun 2024 15:31:40 +0000 Subject: [PATCH 428/465] Add new MotionEvent orientation flags to rust Bug: 263310669 Bug: 346599999 Test: manual (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:cda32662d9784812d767c657a1e573b331fe77a0) Merged-In: I902faf586ad498c0ba8111dd0f89a788ca353866 Change-Id: I902faf586ad498c0ba8111dd0f89a788ca353866 --- libs/input/rust/input.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs index 0574245fa6..564d94dbb4 100644 --- a/libs/input/rust/input.rs +++ b/libs/input/rust/input.rs @@ -207,6 +207,11 @@ bitflags! { const CANCELED = IInputConstants::INPUT_EVENT_FLAG_CANCELED as u32; /// FLAG_NO_FOCUS_CHANGE const NO_FOCUS_CHANGE = IInputConstants::MOTION_EVENT_FLAG_NO_FOCUS_CHANGE as u32; + /// PRIVATE_FLAG_SUPPORTS_ORIENTATION + const PRIVATE_SUPPORTS_ORIENTATION = IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION as u32; + /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION + const PRIVATE_SUPPORTS_DIRECTIONAL_ORIENTATION = + IInputConstants::MOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION as u32; /// FLAG_IS_ACCESSIBILITY_EVENT const IS_ACCESSIBILITY_EVENT = IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT as u32; /// FLAG_TAINTED -- GitLab From b65e2bd8ed1f3a87fe600cd23bf84cecb4105b57 Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Mon, 3 Jun 2024 09:48:16 +0000 Subject: [PATCH 429/465] Dismiss the mouse pointer while typing on keyboard Fixes the borken UX to dismiss the mouse pointer while user is typing on the physical keyboard. Bug: b/338652288 Test: atest inputflinger_tests Change-Id: Ifc4bfd20a44650634d007fbcfc75bf497d5f4623 --- .../inputflinger/PointerChoreographer.cpp | 34 ++++ services/inputflinger/PointerChoreographer.h | 8 + .../dispatcher/InputDispatcher.cpp | 7 + .../include/InputDispatcherPolicyInterface.h | 5 + .../inputflinger/include/NotifyArgsBuilders.h | 7 + .../PointerChoreographerPolicyInterface.h | 3 + .../reader/mapper/KeyboardInputMapper.cpp | 1 - .../tests/FakeInputDispatcherPolicy.cpp | 24 +++ .../tests/FakeInputDispatcherPolicy.h | 5 + .../tests/InputDispatcher_test.cpp | 8 + services/inputflinger/tests/InterfaceMocks.h | 1 + .../tests/KeyboardInputMapper_test.cpp | 45 ----- .../tests/PointerChoreographer_test.cpp | 179 ++++++++++++++++++ 13 files changed, 281 insertions(+), 46 deletions(-) diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp index 7d3a2df550..00dd6ba62b 100644 --- a/services/inputflinger/PointerChoreographer.cpp +++ b/services/inputflinger/PointerChoreographer.cpp @@ -21,6 +21,7 @@ #if defined(__ANDROID__) #include #endif +#include #include #include @@ -137,6 +138,7 @@ PointerChoreographer::PointerChoreographer( mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID), mShowTouchesEnabled(false), mStylusPointerIconEnabled(false), + mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT), mRegisterListener(registerListener), mUnregisterListener(unregisterListener) {} @@ -168,6 +170,7 @@ void PointerChoreographer::notifyConfigurationChanged(const NotifyConfigurationC } void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) { + fadeMouseCursorOnKeyPress(args); mNextListener.notify(args); } @@ -177,6 +180,32 @@ void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) { mNextListener.notify(newArgs); } +void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArgs& args) { + if (args.action == AKEY_EVENT_ACTION_UP || isMetaKey(args.keyCode)) { + return; + } + // Meta state for these keys is ignored for dismissing cursor while typing + constexpr static int32_t ALLOW_FADING_META_STATE_MASK = AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON | + AMETA_SCROLL_LOCK_ON | AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON | AMETA_SHIFT_ON; + if (args.metaState & ~ALLOW_FADING_META_STATE_MASK) { + // Do not fade if any other meta state is active + return; + } + if (!mPolicy.isInputMethodConnectionActive()) { + return; + } + + std::scoped_lock _l(mLock); + ui::LogicalDisplayId targetDisplay = args.displayId; + if (targetDisplay == ui::LogicalDisplayId::INVALID) { + targetDisplay = mCurrentFocusedDisplay; + } + auto it = mMousePointersByDisplay.find(targetDisplay); + if (it != mMousePointersByDisplay.end()) { + it->second->fade(PointerControllerInterface::Transition::GRADUAL); + } +} + NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) { std::scoped_lock _l(mLock); @@ -806,6 +835,11 @@ void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId display } } +void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) { + std::scoped_lock lock(mLock); + mCurrentFocusedDisplay = displayId; +} + PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor( ui::LogicalDisplayId displayId) { std::function()> ctor = diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h index d9b075f3ee..aaf1e3e962 100644 --- a/services/inputflinger/PointerChoreographer.h +++ b/services/inputflinger/PointerChoreographer.h @@ -75,6 +75,11 @@ public: */ virtual void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) = 0; + /** + * Used by Dispatcher to notify changes in the current focused display. + */ + virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0; + /** * This method may be called on any thread (usually by the input manager on a binder thread). */ @@ -97,6 +102,7 @@ public: bool setPointerIcon(std::variant, PointerIconStyle> icon, ui::LogicalDisplayId displayId, DeviceId deviceId) override; void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override; + void setFocusedDisplay(ui::LogicalDisplayId displayId) override; void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; @@ -124,6 +130,7 @@ private: InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock); bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock); + void fadeMouseCursorOnKeyPress(const NotifyKeyArgs& args); NotifyMotionArgs processMotion(const NotifyMotionArgs& args); NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock); @@ -192,6 +199,7 @@ private: bool mShowTouchesEnabled GUARDED_BY(mLock); bool mStylusPointerIconEnabled GUARDED_BY(mLock); std::set mDisplaysWithPointersHidden; + ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(mLock); protected: using WindowListenerRegisterConsumer = std::function( diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 5ed5eb8e33..47c288931d 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -5526,6 +5526,13 @@ void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) { synthesizeCancelationEventsForWindowLocked(windowHandle, options); } mFocusedDisplayId = displayId; + // Enqueue a command to run outside the lock to tell the policy that the focused display + // changed. + auto command = [this]() REQUIRES(mLock) { + scoped_unlock unlock(mLock); + mPolicy.notifyFocusedDisplayChanged(mFocusedDisplayId); + }; + postCommandLocked(std::move(command)); // Only a window on the focused display can have Pointer Capture, so disable the active // Pointer Capture session if there is one, since the focused display changed. diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h index 0f03620f05..65fb76d274 100644 --- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h +++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h @@ -76,6 +76,11 @@ public: InputDeviceSensorAccuracy accuracy) = 0; virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0; + /* + * Notifies the system that focused display has changed. + */ + virtual void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) = 0; + /* Filters an input event. * Return true to dispatch the event unmodified, false to consume the event. * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h index cae638f7bf..5b94d57b8e 100644 --- a/services/inputflinger/include/NotifyArgsBuilders.h +++ b/services/inputflinger/include/NotifyArgsBuilders.h @@ -21,6 +21,7 @@ #include #include #include +#include #include // for nsecs_t, systemTime #include @@ -206,6 +207,12 @@ public: return *this; } + KeyArgsBuilder& metaState(int32_t metaState) { + mMetaState |= metaState; + mMetaState = normalizeMetaState(/*oldMetaState=*/mMetaState); + return *this; + } + NotifyKeyArgs build() const { return {mEventId, mEventTime, diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h index f6dc10997a..7a85c12559 100644 --- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h +++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h @@ -55,6 +55,9 @@ public: */ virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, const FloatPoint& position) = 0; + + /* Returns true if any InputConnection is currently active. */ + virtual bool isInputMethodConnectionActive() = 0; }; } // namespace android diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 91ec62d3d4..25f4893baf 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -493,7 +493,6 @@ std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) { InputReaderContext& context = *getContext(); context.setLastKeyDownTimestamp(downTime); - // TODO(b/338652288): Move cursor fading logic into PointerChoreographer. // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard // shortcuts bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode); diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp index e17ee3a5d9..3df05f4bae 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp @@ -219,6 +219,24 @@ void FakeInputDispatcherPolicy::setConsumeKeyBeforeDispatching(bool consumeKeyBe mConsumeKeyBeforeDispatching = consumeKeyBeforeDispatching; } +void FakeInputDispatcherPolicy::assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay) { + std::unique_lock lock(mLock); + base::ScopedLockAssertion assumeLocked(mLock); + + if (!mFocusedDisplayNotifiedCondition.wait_for(lock, 100ms, + [this, expectedDisplay]() REQUIRES(mLock) { + if (!mNotifiedFocusedDisplay.has_value() || + mNotifiedFocusedDisplay.value() != + expectedDisplay) { + return false; + } + return true; + })) { + ADD_FAILURE() << "Timed out waiting for notifyFocusedDisplayChanged(" << expectedDisplay + << ") to be called."; + } +} + void FakeInputDispatcherPolicy::assertUserActivityNotPoked() { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); @@ -473,4 +491,10 @@ void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal( mFilteredEvent = nullptr; } +void FakeInputDispatcherPolicy::notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) { + std::scoped_lock lock(mLock); + mNotifiedFocusedDisplay = displayId; + mFocusedDisplayNotifiedCondition.notify_all(); +} + } // namespace android diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index 62ff10f8c8..a0f3ea9008 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -116,6 +116,7 @@ public: void assertUnhandledKeyReported(int32_t keycode); void assertUnhandledKeyNotReported(); void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching); + void assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay); private: std::mutex mLock; @@ -126,6 +127,9 @@ private: std::condition_variable mPointerCaptureChangedCondition; + std::optional mNotifiedFocusedDisplay GUARDED_BY(mLock); + std::condition_variable mFocusedDisplayNotifiedCondition; + std::optional mPointerCaptureRequest GUARDED_BY(mLock); // ANR handling std::queue> mAnrApplications GUARDED_BY(mLock); @@ -201,6 +205,7 @@ private: void notifyDropWindow(const sp& token, float x, float y) override; void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, const std::set& uids) override; + void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) override; void assertFilterInputEventWasCalledInternal( const std::function& verify); diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 56a05a3a3c..aa1462a2ff 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -8577,6 +8577,8 @@ public: // Set focus to second display window. // Set focus display to second one. mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID); + mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID); + // Set focus window for second display. mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2); windowInSecondary->setFocusable(true); @@ -11066,6 +11068,7 @@ TEST_F(InputDispatcherPointerCaptureTests, MultiDisplayPointerCapture) { // Make the second display the focused display. mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID); + mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID); // This causes the first window to lose pointer capture, and it's unable to request capture. mWindow->consumeCaptureEvent(false); @@ -13769,4 +13772,9 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) { /*pointerId=*/0)); } +TEST_F(InputDispatcherTest, FocusedDisplayChangeIsNotified) { + mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID); + mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID); +} + } // namespace android::inputdispatcher diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index 44417246d2..16d3193908 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -186,6 +186,7 @@ public: (PointerControllerInterface::ControllerType), (override)); MOCK_METHOD(void, notifyPointerDisplayIdChanged, (ui::LogicalDisplayId displayId, const FloatPoint& position), (override)); + MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override)); }; } // namespace android diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index ada841d28d..ab47cc67b1 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -70,15 +70,6 @@ protected: AINPUT_SOURCE_KEYBOARD); } - void testPointerVisibilityForKeys(const std::vector& keyCodes, bool expectVisible) { - for (int32_t keyCode : keyCodes) { - process(EV_KEY, keyCode, 1); - process(EV_SYN, SYN_REPORT, 0); - process(EV_KEY, keyCode, 0); - process(EV_SYN, SYN_REPORT, 0); - } - } - void testTouchpadTapStateForKeys(const std::vector& keyCodes, const bool expectPrevent) { if (expectPrevent) { @@ -94,42 +85,6 @@ protected: } }; -/** - * Pointer visibility should remain unaffected if there is no active Input Method Connection - */ -TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDoesNotHidePointer) { - testPointerVisibilityForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectVisible= */ true); -} - -/** - * Pointer should hide if there is a active Input Method Connection - */ -TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionHidePointer) { - mFakePolicy->setIsInputMethodConnectionActive(true); - testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false); -} - -/** - * Pointer should still hide if touchpad taps are already disabled - */ -TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) { - mFakePolicy->setIsInputMethodConnectionActive(true); - EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true)); - testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false); -} - -/** - * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is - * active - */ -TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDoesNotHidePointer) { - mFakePolicy->setIsInputMethodConnectionActive(true); - std::vector metaKeys{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT, - KEY_FN, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, - KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK, KEY_SCROLLLOCK}; - testPointerVisibilityForKeys(metaKeys, /* expectVisible= */ true); -} - /** * Touchpad tap should not be disabled if there is no active Input Method Connection */ diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp index 3f2d6ec45c..9a5b6a73f5 100644 --- a/services/inputflinger/tests/PointerChoreographer_test.cpp +++ b/services/inputflinger/tests/PointerChoreographer_test.cpp @@ -2294,6 +2294,185 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) { assertPointerControllerRemoved(pc); } +class PointerVisibilityOnKeyPressTest : public PointerChoreographerTest { +protected: + const std::unordered_map + mMetaKeyStates{{AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON}, + {AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON}, + {AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON}, + {AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON}, + {AKEYCODE_SYM, AMETA_SYM_ON}, + {AKEYCODE_FUNCTION, AMETA_FUNCTION_ON}, + {AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON}, + {AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON}, + {AKEYCODE_META_LEFT, AMETA_META_LEFT_ON}, + {AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON}, + {AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON}, + {AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON}, + {AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON}}; + + void notifyKey(ui::LogicalDisplayId targetDisplay, int32_t keyCode, + int32_t metaState = AMETA_NONE) { + if (metaState == AMETA_NONE && mMetaKeyStates.contains(keyCode)) { + // For simplicity, we always set the corresponding meta state when sending a meta + // keycode. This does not take into consideration when the meta state is updated in + // reality. + metaState = mMetaKeyStates.at(keyCode); + } + mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD) + .displayId(targetDisplay) + .keyCode(keyCode) + .metaState(metaState) + .build()); + mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD) + .displayId(targetDisplay) + .keyCode(keyCode) + .metaState(metaState) + .build()); + } + + void metaKeyCombinationHidesPointer(FakePointerController& pc, int32_t keyCode, + int32_t metaKeyCode) { + ASSERT_TRUE(pc.isPointerShown()); + notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode)); + ASSERT_FALSE(pc.isPointerShown()); + + unfadePointer(); + } + + void metaKeyCombinationDoesNotHidePointer(FakePointerController& pc, int32_t keyCode, + int32_t metaKeyCode) { + ASSERT_TRUE(pc.isPointerShown()); + notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode)); + ASSERT_TRUE(pc.isPointerShown()); + } + + void unfadePointer() { + // unfade pointer by injecting mose hover event + mChoreographer.notifyMotion( + MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE) + .pointer(MOUSE_POINTER) + .deviceId(DEVICE_ID) + .displayId(DISPLAY_ID) + .build()); + } +}; + +TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutImeConnectionDoesNotHidePointer) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Mouse connected + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc->isPointerShown()); + + notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0); + notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A); + notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_CTRL_LEFT); + + ASSERT_TRUE(pc->isPointerShown()); +} + +TEST_F(PointerVisibilityOnKeyPressTest, AlphanumericKeystrokesWithImeConnectionHidePointer) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Mouse connected + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc->isPointerShown()); + + EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true)); + + notifyKey(DISPLAY_ID, AKEYCODE_0); + ASSERT_FALSE(pc->isPointerShown()); + + unfadePointer(); + + notifyKey(DISPLAY_ID, AKEYCODE_A); + ASSERT_FALSE(pc->isPointerShown()); +} + +TEST_F(PointerVisibilityOnKeyPressTest, MetaKeystrokesDoNotHidePointer) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Mouse connected + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc->isPointerShown()); + + EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true)); + + const std::vector metaKeyCodes{AKEYCODE_ALT_LEFT, AKEYCODE_ALT_RIGHT, + AKEYCODE_SHIFT_LEFT, AKEYCODE_SHIFT_RIGHT, + AKEYCODE_SYM, AKEYCODE_FUNCTION, + AKEYCODE_CTRL_LEFT, AKEYCODE_CTRL_RIGHT, + AKEYCODE_META_LEFT, AKEYCODE_META_RIGHT, + AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK, + AKEYCODE_SCROLL_LOCK}; + for (int32_t keyCode : metaKeyCodes) { + notifyKey(ui::LogicalDisplayId::INVALID, keyCode); + } + + ASSERT_TRUE(pc->isPointerShown()); +} + +TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutTargetHidePointerOnlyOnFocusedDisplay) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID})); + mChoreographer.setFocusedDisplay(DISPLAY_ID); + + // Mouse connected + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, + {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID), + generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}}); + auto pc1 = assertPointerControllerCreated(ControllerType::MOUSE); + auto pc2 = assertPointerControllerCreated(ControllerType::MOUSE); + ASSERT_TRUE(pc1->isPointerShown()); + ASSERT_TRUE(pc2->isPointerShown()); + + EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true)); + + notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0); + ASSERT_FALSE(pc1->isPointerShown()); + ASSERT_TRUE(pc2->isPointerShown()); + unfadePointer(); + + notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A); + ASSERT_FALSE(pc1->isPointerShown()); + ASSERT_TRUE(pc2->isPointerShown()); +} + +TEST_F(PointerVisibilityOnKeyPressTest, TestMetaKeyCombinations) { + mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID})); + + // Mouse connected + mChoreographer.notifyInputDevicesChanged( + {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}}); + auto pc = assertPointerControllerCreated(ControllerType::MOUSE); + EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true)); + + // meta key combinations that should hide pointer + metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_LEFT); + metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_RIGHT); + metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_CAPS_LOCK); + metaKeyCombinationHidesPointer(*pc, AKEYCODE_0, AKEYCODE_NUM_LOCK); + metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SCROLL_LOCK); + + // meta key combinations that should not hide pointer + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_LEFT); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_RIGHT); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_LEFT); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_RIGHT); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_SYM); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_FUNCTION); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_LEFT); + metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_RIGHT); +} + class PointerChoreographerWindowInfoListenerTest : public testing::Test {}; TEST_F_WITH_FLAGS( -- GitLab From 5c989f5c72c52cba1fc2264525fac2e58bdf9f94 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Thu, 11 Apr 2024 13:57:14 -0400 Subject: [PATCH 430/465] SF: Isolate modesetting in DisplayModeController Move the per-display state machine for modesetting from DisplayDevice to DMC. In lieu of mStateLock, protect display lookup from multiple threads using a mutex internal to DMC, which fixes the following deadlock: OneShotTimer::loop SF::requestDisplayModes mStateLock SF::commit mStateLock SF::processDisplayChangesLocked (hotplug or resolution change) Scheduler::demotePacesetterDisplay OneShotTimer::stop A notable change is that {initiate,finalize}DisplayModeChange(s) are no longer called under mStateLock, thanks to DMC's granular, internal lock. finalizeDisplayModeChange still locks mStateLock for resolution changes. Add an ActiveModeListener to DMC and register a callback in SF to update the refresh rate overlay, which still lives in DisplayDevice for now. Fixes: 329450361 Bug: 241285876 Test: DisplayModeControllerTest Test: libsurfaceflinger_unittest Change-Id: I30ec756f134d2d67a70ac8797008dc792eac035e --- .../Display/DisplayModeController.cpp | 199 ++++++++++++++- .../Display/DisplayModeController.h | 99 ++++++-- services/surfaceflinger/DisplayDevice.cpp | 108 +------- services/surfaceflinger/DisplayDevice.h | 58 +---- services/surfaceflinger/SurfaceFlinger.cpp | 198 ++++++++------- services/surfaceflinger/SurfaceFlinger.h | 17 +- .../surfaceflinger/tests/unittests/Android.bp | 2 +- .../DisplayDevice_InitiateModeChange.cpp | 152 ------------ .../unittests/DisplayModeControllerTest.cpp | 234 ++++++++++++++++++ .../SurfaceFlinger_DisplayModeSwitching.cpp | 149 +++++------ ...nger_SetupNewDisplayDeviceInternalTest.cpp | 13 +- .../tests/unittests/TestableSurfaceFlinger.h | 14 +- 12 files changed, 733 insertions(+), 510 deletions(-) delete mode 100644 services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp create mode 100644 services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp index f093384921..a6a9bec3c3 100644 --- a/services/surfaceflinger/Display/DisplayModeController.cpp +++ b/services/surfaceflinger/Display/DisplayModeController.cpp @@ -20,30 +20,221 @@ #include "Display/DisplayModeController.h" #include "Display/DisplaySnapshot.h" +#include "DisplayHardware/HWComposer.h" +#include +#include +#include #include namespace android::display { +template +inline std::string DisplayModeController::Display::concatId(const char (&str)[N]) const { + return std::string(ftl::Concat(str, ' ', snapshot.get().displayId().value).str()); +} + +DisplayModeController::Display::Display(DisplaySnapshotRef snapshot, + RefreshRateSelectorPtr selectorPtr) + : snapshot(snapshot), + selectorPtr(std::move(selectorPtr)), + pendingModeFpsTrace(concatId("PendingModeFps")), + activeModeFpsTrace(concatId("ActiveModeFps")), + renderRateFpsTrace(concatId("RenderRateFps")), + hasDesiredModeTrace(concatId("HasDesiredMode"), false) {} + +void DisplayModeController::registerDisplay(PhysicalDisplayId displayId, + DisplaySnapshotRef snapshotRef, + RefreshRateSelectorPtr selectorPtr) { + std::lock_guard lock(mDisplayLock); + mDisplays.emplace_or_replace(displayId, std::make_unique(snapshotRef, selectorPtr)); +} + void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef, DisplayModeId activeModeId, scheduler::RefreshRateSelector::Config config) { const auto& snapshot = snapshotRef.get(); const auto displayId = snapshot.displayId(); - mDisplays.emplace_or_replace(displayId, snapshotRef, snapshot.displayModes(), activeModeId, - config); + std::lock_guard lock(mDisplayLock); + mDisplays.emplace_or_replace(displayId, + std::make_unique(snapshotRef, snapshot.displayModes(), + activeModeId, config)); } void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) { + std::lock_guard lock(mDisplayLock); const bool ok = mDisplays.erase(displayId); ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str()); } -auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) -> RefreshRateSelectorPtr { +auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) const + -> RefreshRateSelectorPtr { + std::lock_guard lock(mDisplayLock); return mDisplays.get(displayId) - .transform([](const Display& display) { return display.selectorPtr; }) + .transform([](const DisplayPtr& displayPtr) { return displayPtr->selectorPtr; }) .value_or(nullptr); } +auto DisplayModeController::setDesiredMode(PhysicalDisplayId displayId, + DisplayModeRequest&& desiredMode) -> DesiredModeAction { + std::lock_guard lock(mDisplayLock); + const auto& displayPtr = + FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get(); + + { + ATRACE_NAME(displayPtr->concatId(__func__).c_str()); + ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str()); + + std::scoped_lock lock(displayPtr->desiredModeLock); + + if (auto& desiredModeOpt = displayPtr->desiredModeOpt) { + // A mode transition was already scheduled, so just override the desired mode. + const bool emitEvent = desiredModeOpt->emitEvent; + const bool force = desiredModeOpt->force; + desiredModeOpt = std::move(desiredMode); + desiredModeOpt->emitEvent |= emitEvent; + if (FlagManager::getInstance().connected_display()) { + desiredModeOpt->force |= force; + } + return DesiredModeAction::None; + } + + // If the desired mode is already active... + const auto activeMode = displayPtr->selectorPtr->getActiveMode(); + if (const auto& desiredModePtr = desiredMode.mode.modePtr; + !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) { + if (activeMode == desiredMode.mode) { + return DesiredModeAction::None; + } + + // ...but the render rate changed: + setActiveModeLocked(displayId, desiredModePtr->getId(), desiredModePtr->getVsyncRate(), + desiredMode.mode.fps); + return DesiredModeAction::InitiateRenderRateSwitch; + } + + // Restore peak render rate to schedule the next frame as soon as possible. + setActiveModeLocked(displayId, activeMode.modePtr->getId(), + activeMode.modePtr->getVsyncRate(), activeMode.modePtr->getPeakFps()); + + // Initiate a mode change. + displayPtr->desiredModeOpt = std::move(desiredMode); + displayPtr->hasDesiredModeTrace = true; + } + + return DesiredModeAction::InitiateDisplayModeSwitch; +} + +auto DisplayModeController::getDesiredMode(PhysicalDisplayId displayId) const + -> DisplayModeRequestOpt { + std::lock_guard lock(mDisplayLock); + const auto& displayPtr = + FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get(); + + { + std::scoped_lock lock(displayPtr->desiredModeLock); + return displayPtr->desiredModeOpt; + } +} + +auto DisplayModeController::getPendingMode(PhysicalDisplayId displayId) const + -> DisplayModeRequestOpt { + std::lock_guard lock(mDisplayLock); + const auto& displayPtr = + FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get(); + + { + std::scoped_lock lock(displayPtr->desiredModeLock); + return displayPtr->pendingModeOpt; + } +} + +bool DisplayModeController::isModeSetPending(PhysicalDisplayId displayId) const { + std::lock_guard lock(mDisplayLock); + const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get(); + + { + std::scoped_lock lock(displayPtr->desiredModeLock); + return displayPtr->isModeSetPending; + } +} + +scheduler::FrameRateMode DisplayModeController::getActiveMode(PhysicalDisplayId displayId) const { + return selectorPtrFor(displayId)->getActiveMode(); +} + +void DisplayModeController::clearDesiredMode(PhysicalDisplayId displayId) { + std::lock_guard lock(mDisplayLock); + const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); + + { + std::scoped_lock lock(displayPtr->desiredModeLock); + displayPtr->desiredModeOpt.reset(); + displayPtr->hasDesiredModeTrace = false; + } +} + +bool DisplayModeController::initiateModeChange(PhysicalDisplayId displayId, + DisplayModeRequest&& desiredMode, + const hal::VsyncPeriodChangeConstraints& constraints, + hal::VsyncPeriodChangeTimeline& outTimeline) { + std::lock_guard lock(mDisplayLock); + const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get(); + + // TODO: b/255635711 - Flow the DisplayModeRequest through the desired/pending/active states. + // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not + // cleared until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been + // consumed at this point, so clear the `force` flag to prevent an endless loop of + // `initiateModeChange`. + if (FlagManager::getInstance().connected_display()) { + std::scoped_lock lock(displayPtr->desiredModeLock); + if (displayPtr->desiredModeOpt) { + displayPtr->desiredModeOpt->force = false; + } + } + + displayPtr->pendingModeOpt = std::move(desiredMode); + displayPtr->isModeSetPending = true; + + const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr; + + if (mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(), constraints, + &outTimeline) != OK) { + return false; + } + + ATRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue()); + return true; +} + +void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId, + Fps vsyncRate, Fps renderFps) { + std::lock_guard lock(mDisplayLock); + setActiveModeLocked(displayId, modeId, vsyncRate, renderFps); + + const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); + displayPtr->isModeSetPending = false; +} + +void DisplayModeController::setActiveMode(PhysicalDisplayId displayId, DisplayModeId modeId, + Fps vsyncRate, Fps renderFps) { + std::lock_guard lock(mDisplayLock); + setActiveModeLocked(displayId, modeId, vsyncRate, renderFps); +} + +void DisplayModeController::setActiveModeLocked(PhysicalDisplayId displayId, DisplayModeId modeId, + Fps vsyncRate, Fps renderFps) { + const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get(); + + ATRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue()); + ATRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue()); + + displayPtr->selectorPtr->setActiveMode(modeId, renderFps); + + if (mActiveModeListener) { + mActiveModeListener(displayId, vsyncRate, renderFps); + } +} + } // namespace android::display diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h index b6a6bee714..258b04b876 100644 --- a/services/surfaceflinger/Display/DisplayModeController.h +++ b/services/surfaceflinger/Display/DisplayModeController.h @@ -17,16 +17,26 @@ #pragma once #include +#include +#include #include #include +#include +#include #include #include +#include "Display/DisplayModeRequest.h" #include "Display/DisplaySnapshotRef.h" #include "DisplayHardware/DisplayMode.h" #include "Scheduler/RefreshRateSelector.h" #include "ThreadContext.h" +#include "TracedOrdinal.h" + +namespace android { +class HWComposer; +} // namespace android namespace android::display { @@ -34,40 +44,97 @@ namespace android::display { // certain heuristic signals. class DisplayModeController { public: - // The referenced DisplaySnapshot must outlive the registration. - void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config) - REQUIRES(kMainThreadContext); - void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext); + using ActiveModeListener = ftl::Function; + + DisplayModeController() = default; - // TODO(b/241285876): Remove once ownership is no longer shared with DisplayDevice. + void setHwComposer(HWComposer* composerPtr) { mComposerPtr = composerPtr; } + void setActiveModeListener(const ActiveModeListener& listener) { + mActiveModeListener = listener; + } + + // TODO: b/241285876 - Remove once ownership is no longer shared with DisplayDevice. using RefreshRateSelectorPtr = std::shared_ptr; + // Used by tests to inject an existing RefreshRateSelector. + // TODO: b/241285876 - Remove this. + void registerDisplay(PhysicalDisplayId, DisplaySnapshotRef, RefreshRateSelectorPtr) + EXCLUDES(mDisplayLock); + + // The referenced DisplaySnapshot must outlive the registration. + void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + // Returns `nullptr` if the display is no longer registered (or never was). - RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) REQUIRES(kMainThreadContext); + RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) const EXCLUDES(mDisplayLock); - // Used by tests to inject an existing RefreshRateSelector. - // TODO(b/241285876): Remove this. - void registerDisplay(PhysicalDisplayId displayId, DisplaySnapshotRef snapshotRef, - RefreshRateSelectorPtr selectorPtr) { - mDisplays.emplace_or_replace(displayId, snapshotRef, selectorPtr); - } + enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch }; + + DesiredModeAction setDesiredMode(PhysicalDisplayId, DisplayModeRequest&&) + EXCLUDES(mDisplayLock); + + using DisplayModeRequestOpt = ftl::Optional; + + DisplayModeRequestOpt getDesiredMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock); + void clearDesiredMode(PhysicalDisplayId) EXCLUDES(mDisplayLock); + + DisplayModeRequestOpt getPendingMode(PhysicalDisplayId) const REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); + bool isModeSetPending(PhysicalDisplayId) const REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); + + scheduler::FrameRateMode getActiveMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock); + + bool initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&, + const hal::VsyncPeriodChangeConstraints&, + hal::VsyncPeriodChangeTimeline& outTimeline) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + + void finalizeModeChange(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + + void setActiveMode(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps) + EXCLUDES(mDisplayLock); private: struct Display { - Display(DisplaySnapshotRef snapshot, RefreshRateSelectorPtr selectorPtr) - : snapshot(snapshot), selectorPtr(std::move(selectorPtr)) {} + template + std::string concatId(const char (&)[N]) const; + Display(DisplaySnapshotRef, RefreshRateSelectorPtr); Display(DisplaySnapshotRef snapshot, DisplayModes modes, DisplayModeId activeModeId, scheduler::RefreshRateSelector::Config config) : Display(snapshot, std::make_shared(std::move(modes), activeModeId, config)) {} - const DisplaySnapshotRef snapshot; const RefreshRateSelectorPtr selectorPtr; + + const std::string pendingModeFpsTrace; + const std::string activeModeFpsTrace; + const std::string renderRateFpsTrace; + + std::mutex desiredModeLock; + DisplayModeRequestOpt desiredModeOpt GUARDED_BY(desiredModeLock); + TracedOrdinal hasDesiredModeTrace GUARDED_BY(desiredModeLock); + + DisplayModeRequestOpt pendingModeOpt GUARDED_BY(kMainThreadContext); + bool isModeSetPending GUARDED_BY(kMainThreadContext) = false; }; - ui::PhysicalDisplayMap mDisplays; + using DisplayPtr = std::unique_ptr; + + void setActiveModeLocked(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps) + REQUIRES(mDisplayLock); + + // Set once when initializing the DisplayModeController, which the HWComposer must outlive. + HWComposer* mComposerPtr = nullptr; + + ActiveModeListener mActiveModeListener; + + mutable std::mutex mDisplayLock; + ui::PhysicalDisplayMap mDisplays GUARDED_BY(mDisplayLock); }; } // namespace android::display diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index a57e626224..27ea4a9865 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -24,7 +24,6 @@ #define ATRACE_TAG ATRACE_TAG_GRAPHICS -#include #include #include #include @@ -36,6 +35,7 @@ #include #include #include +#include #include #include @@ -64,15 +64,11 @@ DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs& args) mDisplayToken(args.displayToken), mSequenceId(args.sequenceId), mCompositionDisplay{args.compositionDisplay}, - mPendingModeFpsTrace(concatId("PendingModeFps")), - mActiveModeFpsTrace(concatId("ActiveModeFps")), - mRenderRateFpsTrace(concatId("RenderRateFps")), mPhysicalOrientation(args.physicalOrientation), mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode), mIsPrimary(args.isPrimary), mRequestedRefreshRate(args.requestedRefreshRate), - mRefreshRateSelector(std::move(args.refreshRateSelector)), - mHasDesiredModeTrace(concatId("HasDesiredMode"), false) { + mRefreshRateSelector(std::move(args.refreshRateSelector)) { mCompositionDisplay->editState().isSecure = args.isSecure; mCompositionDisplay->editState().isProtected = args.isProtected; mCompositionDisplay->createRenderSurface( @@ -204,47 +200,6 @@ bool DisplayDevice::isPoweredOn() const { return mPowerMode != hal::PowerMode::OFF; } -void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) { - ATRACE_INT(mActiveModeFpsTrace.c_str(), vsyncRate.getIntValue()); - ATRACE_INT(mRenderRateFpsTrace.c_str(), renderFps.getIntValue()); - - mRefreshRateSelector->setActiveMode(modeId, renderFps); - updateRefreshRateOverlayRate(vsyncRate, renderFps); -} - -bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode, - const hal::VsyncPeriodChangeConstraints& constraints, - hal::VsyncPeriodChangeTimeline& outTimeline) { - // TODO(b/255635711): Flow the DisplayModeRequest through the desired/pending/active states. For - // now, `desiredMode` and `mDesiredModeOpt` are one and the same, but the latter is not cleared - // until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been consumed - // at this point, so clear the `force` flag to prevent an endless loop of `initiateModeChange`. - if (FlagManager::getInstance().connected_display()) { - std::scoped_lock lock(mDesiredModeLock); - if (mDesiredModeOpt) { - mDesiredModeOpt->force = false; - } - } - - mPendingModeOpt = std::move(desiredMode); - mIsModeSetPending = true; - - const auto& mode = *mPendingModeOpt->mode.modePtr; - - if (mHwComposer.setActiveModeWithConstraints(getPhysicalId(), mode.getHwcId(), constraints, - &outTimeline) != OK) { - return false; - } - - ATRACE_INT(mPendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue()); - return true; -} - -void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) { - setActiveMode(modeId, vsyncRate, renderFps); - mIsModeSetPending = false; -} - nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const { const auto physicalId = getPhysicalId(); if (!mHwComposer.isConnected(physicalId)) { @@ -450,8 +405,9 @@ void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) { } } -void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, - bool showRenderRate, bool showInMiddle) { +void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, Fps refreshRate, + Fps renderFps, bool showSpinner, bool showRenderRate, + bool showInMiddle) { if (!enable) { mRefreshRateOverlay.reset(); return; @@ -479,8 +435,7 @@ void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool sh if (mRefreshRateOverlay) { mRefreshRateOverlay->setLayerStack(getLayerStack()); mRefreshRateOverlay->setViewport(getSize()); - updateRefreshRateOverlayRate(getActiveMode().modePtr->getVsyncRate(), getActiveMode().fps, - setByHwc); + updateRefreshRateOverlayRate(refreshRate, renderFps, setByHwc); } } @@ -531,57 +486,6 @@ void DisplayDevice::animateOverlay() { } } -auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction { - ATRACE_NAME(concatId(__func__).c_str()); - ALOGD("%s %s", concatId(__func__).c_str(), to_string(desiredMode).c_str()); - - std::scoped_lock lock(mDesiredModeLock); - if (mDesiredModeOpt) { - // A mode transition was already scheduled, so just override the desired mode. - const bool emitEvent = mDesiredModeOpt->emitEvent; - const bool force = mDesiredModeOpt->force; - mDesiredModeOpt = std::move(desiredMode); - mDesiredModeOpt->emitEvent |= emitEvent; - if (FlagManager::getInstance().connected_display()) { - mDesiredModeOpt->force |= force; - } - return DesiredModeAction::None; - } - - // If the desired mode is already active... - const auto activeMode = refreshRateSelector().getActiveMode(); - if (const auto& desiredModePtr = desiredMode.mode.modePtr; - !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) { - if (activeMode == desiredMode.mode) { - return DesiredModeAction::None; - } - - // ...but the render rate changed: - setActiveMode(desiredModePtr->getId(), desiredModePtr->getVsyncRate(), - desiredMode.mode.fps); - return DesiredModeAction::InitiateRenderRateSwitch; - } - - setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(), - activeMode.modePtr->getPeakFps()); - - // Initiate a mode change. - mDesiredModeOpt = std::move(desiredMode); - mHasDesiredModeTrace = true; - return DesiredModeAction::InitiateDisplayModeSwitch; -} - -auto DisplayDevice::getDesiredMode() const -> DisplayModeRequestOpt { - std::scoped_lock lock(mDesiredModeLock); - return mDesiredModeOpt; -} - -void DisplayDevice::clearDesiredMode() { - std::scoped_lock lock(mDesiredModeLock); - mDesiredModeOpt.reset(); - mHasDesiredModeTrace = false; -} - void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) { using fps_approx_ops::operator<=; if (mRequestedRefreshRate <= 0_Hz) { diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index a21559fb45..3cc8cf5d63 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -23,8 +23,6 @@ #include #include #include -#include -#include #include #include #include @@ -42,7 +40,6 @@ #include #include -#include "Display/DisplayModeRequest.h" #include "DisplayHardware/DisplayMode.h" #include "DisplayHardware/Hal.h" #include "DisplayHardware/PowerAdvisor.h" @@ -183,37 +180,6 @@ public: ui::Dataspace getCompositionDataSpace() const; - /* ------------------------------------------------------------------------ - * Display mode management. - */ - - enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch }; - - DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock); - - using DisplayModeRequestOpt = ftl::Optional; - - DisplayModeRequestOpt getDesiredMode() const EXCLUDES(mDesiredModeLock); - void clearDesiredMode() EXCLUDES(mDesiredModeLock); - - DisplayModeRequestOpt getPendingMode() const REQUIRES(kMainThreadContext) { - return mPendingModeOpt; - } - bool isModeSetPending() const REQUIRES(kMainThreadContext) { return mIsModeSetPending; } - - scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) { - return mRefreshRateSelector->getActiveMode(); - } - - void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps); - - bool initiateModeChange(display::DisplayModeRequest&&, const hal::VsyncPeriodChangeConstraints&, - hal::VsyncPeriodChangeTimeline& outTimeline) - REQUIRES(kMainThreadContext); - - void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps) - REQUIRES(kMainThreadContext); - scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; } // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice. @@ -221,13 +187,14 @@ public: return mRefreshRateSelector; } - void animateOverlay(); - // Enables an overlay to be displayed with the current refresh rate - void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate, - bool showInMiddle) REQUIRES(kMainThreadContext); + // TODO(b/241285876): Move overlay to DisplayModeController. + void enableRefreshRateOverlay(bool enable, bool setByHwc, Fps refreshRate, Fps renderFps, + bool showSpinner, bool showRenderRate, bool showInMiddle) + REQUIRES(kMainThreadContext); void updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc = false); bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } + void animateOverlay(); bool onKernelTimerChanged(std::optional, bool timerExpired); // Enables an overlay to be display with the hdr/sdr ratio @@ -249,11 +216,6 @@ public: void dump(utils::Dumper&) const; private: - template - inline std::string concatId(const char (&str)[N]) const { - return std::string(ftl::Concat(str, ' ', getId().value).str()); - } - const sp mFlinger; HWComposer& mHwComposer; const wp mDisplayToken; @@ -262,9 +224,6 @@ private: const std::shared_ptr mCompositionDisplay; std::string mDisplayName; - std::string mPendingModeFpsTrace; - std::string mActiveModeFpsTrace; - std::string mRenderRateFpsTrace; const ui::Rotation mPhysicalOrientation; ui::Rotation mOrientation = ui::ROTATION_0; @@ -296,13 +255,6 @@ private: std::unique_ptr mHdrSdrRatioOverlay; // This parameter is only used for hdr/sdr ratio overlay float mHdrSdrRatio = 1.0f; - - mutable std::mutex mDesiredModeLock; - DisplayModeRequestOpt mDesiredModeOpt GUARDED_BY(mDesiredModeLock); - TracedOrdinal mHasDesiredModeTrace GUARDED_BY(mDesiredModeLock); - - DisplayModeRequestOpt mPendingModeOpt GUARDED_BY(kMainThreadContext); - bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false; }; struct DisplayDeviceState { diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 59345db67c..b1d7750a92 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -891,8 +891,12 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { } mCompositionEngine->setTimeStats(mTimeStats); + mCompositionEngine->setHwComposer(getFactory().createHWComposer(mHwcServiceName)); - mCompositionEngine->getHwComposer().setCallback(*this); + auto& composer = mCompositionEngine->getHwComposer(); + composer.setCallback(*this); + mDisplayModeController.setHwComposer(&composer); + ClientCache::getInstance().setRenderEngine(&getRenderEngine()); mHasReliablePresentFences = @@ -931,6 +935,20 @@ void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) { // initializing the Scheduler after configureLocked, once decoupled from DisplayDevice. initScheduler(display); + // Start listening after creating the Scheduler, since the listener calls into it. + mDisplayModeController.setActiveModeListener( + display::DisplayModeController::ActiveModeListener::make( + [this](PhysicalDisplayId displayId, Fps vsyncRate, Fps renderRate) { + // This callback cannot lock mStateLock, as some callers already lock it. + // Instead, switch context to the main thread. + static_cast(mScheduler->schedule([=, + this]() FTL_FAKE_GUARD(mStateLock) { + if (const auto display = getDisplayDeviceLocked(displayId)) { + display->updateRefreshRateOverlayRate(vsyncRate, renderRate); + } + })); + })); + mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) { auto snapshot = perfetto::protos::LayersSnapshotProto{}; mScheduler @@ -1296,19 +1314,19 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) { ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str()); - const auto display = getDisplayDeviceLocked(displayId); - if (!display) { - ALOGW("%s: display is no longer valid", __func__); - return; - } - const bool emitEvent = desiredMode.emitEvent; - switch (display->setDesiredMode(std::move(desiredMode))) { - case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch: - // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler. - mScheduler->setRenderRate(displayId, display->refreshRateSelector().getActiveMode().fps, - /*applyImmediately*/ true); + using DesiredModeAction = display::DisplayModeController::DesiredModeAction; + + switch (mDisplayModeController.setDesiredMode(displayId, std::move(desiredMode))) { + case DesiredModeAction::InitiateDisplayModeSwitch: { + const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId); + if (!selectorPtr) break; + + const Fps renderRate = selectorPtr->getActiveMode().fps; + + // DisplayModeController::setDesiredMode updated the render rate, so inform Scheduler. + mScheduler->setRenderRate(displayId, renderRate, true /* applyImmediately */); // Schedule a new frame to initiate the display mode switch. scheduleComposite(FrameHint::kNone); @@ -1328,7 +1346,8 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) { mScheduler->setModeChangePending(true); break; - case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch: + } + case DesiredModeAction::InitiateRenderRateSwitch: mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false); if (displayId == mActiveDisplayId) { @@ -1339,7 +1358,7 @@ void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& desiredMode) { dispatchDisplayModeChangeEvent(displayId, mode); } break; - case DisplayDevice::DesiredModeAction::None: + case DesiredModeAction::None: break; } } @@ -1395,11 +1414,10 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const spmode; - if (display.getActiveMode().modePtr->getResolution() != activeMode.modePtr->getResolution()) { - auto& state = mCurrentState.displays.editValueFor(display.getDisplayToken()); + if (const auto oldResolution = + mDisplayModeController.getActiveMode(displayId).modePtr->getResolution(); + oldResolution != activeMode.modePtr->getResolution()) { + Mutex::Autolock lock(mStateLock); + + auto& state = mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId)); // We need to generate new sequenceId in order to recreate the display (and this // way the framebuffer). state.sequenceId = DisplayDeviceState{}.sequenceId; @@ -1420,8 +1442,8 @@ void SurfaceFlinger::finalizeDisplayModeChange(DisplayDevice& display) { return; } - display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(), - activeMode.fps); + mDisplayModeController.finalizeModeChange(displayId, activeMode.modePtr->getId(), + activeMode.modePtr->getVsyncRate(), activeMode.fps); if (displayId == mActiveDisplayId) { mScheduler->updatePhaseConfiguration(activeMode.fps); @@ -1432,21 +1454,20 @@ void SurfaceFlinger::finalizeDisplayModeChange(DisplayDevice& display) { } } -void SurfaceFlinger::dropModeRequest(const sp& display) { - display->clearDesiredMode(); - if (display->getPhysicalId() == mActiveDisplayId) { +void SurfaceFlinger::dropModeRequest(PhysicalDisplayId displayId) { + mDisplayModeController.clearDesiredMode(displayId); + if (displayId == mActiveDisplayId) { // TODO(b/255635711): Check for pending mode changes on other displays. mScheduler->setModeChangePending(false); } } -void SurfaceFlinger::applyActiveMode(const sp& display) { - const auto activeModeOpt = display->getDesiredMode(); +void SurfaceFlinger::applyActiveMode(PhysicalDisplayId displayId) { + const auto activeModeOpt = mDisplayModeController.getDesiredMode(displayId); auto activeModePtr = activeModeOpt->mode.modePtr; - const auto displayId = activeModePtr->getPhysicalDisplayId(); const auto renderFps = activeModeOpt->mode.fps; - dropModeRequest(display); + dropModeRequest(displayId); constexpr bool kAllowToEnable = true; mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take()); @@ -1462,11 +1483,8 @@ void SurfaceFlinger::initiateDisplayModeChanges() { std::optional displayToUpdateImmediately; - for (const auto& [id, physical] : mPhysicalDisplays) { - const auto display = getDisplayDeviceLocked(id); - if (!display) continue; - - auto desiredModeOpt = display->getDesiredMode(); + for (const auto& [displayId, physical] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) { + auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId); if (!desiredModeOpt) { continue; } @@ -1483,19 +1501,21 @@ void SurfaceFlinger::initiateDisplayModeChanges() { ALOGV("%s changing active mode to %d(%s) for display %s", __func__, ftl::to_underlying(desiredModeId), to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(), - to_string(display->getId()).c_str()); + to_string(displayId).c_str()); if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) && - display->getActiveMode() == desiredModeOpt->mode) { - applyActiveMode(display); + mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) { + applyActiveMode(displayId); continue; } + const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId); + // Desired active mode was set, it is different than the mode currently in use, however // allowed modes might have changed by the time we process the refresh. // Make sure the desired mode is still allowed - if (!display->refreshRateSelector().isModeAllowed(desiredModeOpt->mode)) { - dropModeRequest(display); + if (!selectorPtr->isModeAllowed(desiredModeOpt->mode)) { + dropModeRequest(displayId); continue; } @@ -1505,11 +1525,12 @@ void SurfaceFlinger::initiateDisplayModeChanges() { constraints.seamlessRequired = false; hal::VsyncPeriodChangeTimeline outTimeline; - if (!display->initiateModeChange(std::move(*desiredModeOpt), constraints, outTimeline)) { + if (!mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt), + constraints, outTimeline)) { continue; } - display->refreshRateSelector().onModeChangeInitiated(); + selectorPtr->onModeChangeInitiated(); mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline); if (outTimeline.refreshRequired) { @@ -1518,17 +1539,18 @@ void SurfaceFlinger::initiateDisplayModeChanges() { // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange` // for all displays. This was only needed when the loop iterated over `mDisplays` rather // than `mPhysicalDisplays`. - displayToUpdateImmediately = display->getPhysicalId(); + displayToUpdateImmediately = displayId; } } if (displayToUpdateImmediately) { - const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately); - finalizeDisplayModeChange(*display); + const auto displayId = *displayToUpdateImmediately; + finalizeDisplayModeChange(displayId); - const auto desiredModeOpt = display->getDesiredMode(); - if (desiredModeOpt && display->getActiveMode() == desiredModeOpt->mode) { - applyActiveMode(display); + const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId); + if (desiredModeOpt && + mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) { + applyActiveMode(displayId); } } } @@ -2276,8 +2298,10 @@ void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos : data.vsyncPeriodNanos); ATRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue()); - display->updateRefreshRateOverlayRate(refreshRate, display->getActiveMode().fps, - /* showRefreshRate */ true); + + const auto renderRate = mDisplayModeController.getActiveMode(*displayIdOpt).fps; + constexpr bool kSetByHwc = true; + display->updateRefreshRateOverlayRate(refreshRate, renderRate, kSetByHwc); } } })); @@ -2585,33 +2609,18 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, // If a mode set is pending and the fence hasn't fired yet, wait for the next commit. if (std::any_of(frameTargets.begin(), frameTargets.end(), - [this](const auto& pair) FTL_FAKE_GUARD(mStateLock) - FTL_FAKE_GUARD(kMainThreadContext) { - if (!pair.second->isFramePending()) return false; - - if (const auto display = getDisplayDeviceLocked(pair.first)) { - return display->isModeSetPending(); - } - - return false; - })) { + [this](const auto& pair) FTL_FAKE_GUARD(kMainThreadContext) { + const auto [displayId, target] = pair; + return target->isFramePending() && + mDisplayModeController.isModeSetPending(displayId); + })) { mScheduler->scheduleFrame(); return false; } - { - Mutex::Autolock lock(mStateLock); - - for (const auto [id, target] : frameTargets) { - // TODO(b/241285876): This is `nullptr` when the DisplayDevice is about to be removed in - // this commit, since the PhysicalDisplay has already been removed. Rather than checking - // for `nullptr` below, change Scheduler::onFrameSignal to filter out the FrameTarget of - // the removed display. - const auto display = getDisplayDeviceLocked(id); - - if (display && display->isModeSetPending()) { - finalizeDisplayModeChange(*display); - } + for (const auto [displayId, _] : frameTargets) { + if (mDisplayModeController.isModeSetPending(displayId)) { + finalizeDisplayModeChange(displayId); } } @@ -2646,8 +2655,8 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, mPowerAdvisor->setFrameDelay(frameDelay); mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration); - const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get(); - const Period idealVsyncPeriod = display->getActiveMode().fps.getPeriod(); + const Period idealVsyncPeriod = + mDisplayModeController.getActiveMode(pacesetterId).fps.getPeriod(); mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod); } @@ -2710,9 +2719,10 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, ? &mLayerHierarchyBuilder.getHierarchy() : nullptr, updateAttachedChoreographer); - initiateDisplayModeChanges(); } + initiateDisplayModeChanges(); + updateCursorAsync(); if (!mustComposite) { updateInputFlinger(vsyncId, pacesetterFrameTarget.frameBeginTime()); @@ -3758,7 +3768,8 @@ sp SurfaceFlinger::setupNewDisplayDeviceInternal( if (const auto& physical = state.physical) { const auto& mode = *physical->activeMode; - display->setActiveMode(mode.getId(), mode.getVsyncRate(), mode.getVsyncRate()); + mDisplayModeController.setActiveMode(physical->id, mode.getId(), mode.getVsyncRate(), + mode.getVsyncRate()); } display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack)); @@ -3937,7 +3948,9 @@ void SurfaceFlinger::processDisplayChanged(const wp& displayToken, // TODO(b/175678251) Call a listener instead. if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) { - mScheduler->resetPhaseConfiguration(display->getActiveMode().fps); + const Fps refreshRate = + mDisplayModeController.getActiveMode(display->getPhysicalId()).fps; + mScheduler->resetPhaseConfiguration(refreshRate); } } return; @@ -7684,9 +7697,10 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { if (!display->isRefreshRateOverlayEnabled()) return; const auto desiredModeIdOpt = - display->getDesiredMode().transform([](const display::DisplayModeRequest& request) { - return request.mode.modePtr->getId(); - }); + mDisplayModeController.getDesiredMode(display->getPhysicalId()) + .transform([](const display::DisplayModeRequest& request) { + return request.mode.modePtr->getId(); + }); const bool timerExpired = mKernelIdleTimerEnabled && expired; @@ -8921,20 +8935,26 @@ status_t SurfaceFlinger::setSmallAreaDetectionThreshold(int32_t appId, float thr void SurfaceFlinger::enableRefreshRateOverlay(bool enable) { bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG); - for (const auto& [id, display] : mPhysicalDisplays) { - if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal || + for (const auto& [displayId, physical] : mPhysicalDisplays) { + if (physical.snapshot().connectionType() == ui::DisplayConnectionType::Internal || FlagManager::getInstance().refresh_rate_overlay_on_external_display()) { - if (const auto device = getDisplayDeviceLocked(id)) { - const auto enableOverlay = [&](const bool setByHwc) FTL_FAKE_GUARD( - kMainThreadContext) { - device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner, - mRefreshRateOverlayRenderRate, - mRefreshRateOverlayShowInMiddle); + if (const auto display = getDisplayDeviceLocked(displayId)) { + const auto enableOverlay = [&](bool setByHwc) FTL_FAKE_GUARD(kMainThreadContext) { + const auto activeMode = mDisplayModeController.getActiveMode(displayId); + const Fps refreshRate = activeMode.modePtr->getVsyncRate(); + const Fps renderFps = activeMode.fps; + + display->enableRefreshRateOverlay(enable, setByHwc, refreshRate, renderFps, + mRefreshRateOverlaySpinner, + mRefreshRateOverlayRenderRate, + mRefreshRateOverlayShowInMiddle); }; + enableOverlay(setByHwc); if (setByHwc) { const auto status = - getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable); + getHwComposer().setRefreshRateChangedCallbackDebugEnabled(displayId, + enable); if (status != NO_ERROR) { ALOGE("Error %s refresh rate changed callback debug", enable ? "enabling" : "disabling"); @@ -9090,7 +9110,7 @@ void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveD mActiveDisplayId = activeDisplay.getPhysicalId(); activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true); - mScheduler->resetPhaseConfiguration(activeDisplay.getActiveMode().fps); + mScheduler->resetPhaseConfiguration(mDisplayModeController.getActiveMode(mActiveDisplayId).fps); // TODO(b/255635711): Check for pending mode changes on other displays. mScheduler->setModeChangePending(false); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 12307172f7..ee541c4364 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -737,12 +737,12 @@ private: status_t setActiveModeFromBackdoor(const sp&, DisplayModeId, Fps minFps, Fps maxFps); - void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext); - void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext); + void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) EXCLUDES(mStateLock); + void finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext) + EXCLUDES(mStateLock); - // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler. - void dropModeRequest(const sp&) REQUIRES(mStateLock); - void applyActiveMode(const sp&) REQUIRES(mStateLock); + void dropModeRequest(PhysicalDisplayId) REQUIRES(kMainThreadContext); + void applyActiveMode(PhysicalDisplayId) REQUIRES(kMainThreadContext); // Called on the main thread in response to setPowerMode() void setPowerModeInternal(const sp& display, hal::PowerMode mode) @@ -1098,8 +1098,7 @@ private: const DisplayDeviceState& drawingState) REQUIRES(mStateLock, kMainThreadContext); - void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&) - REQUIRES(mStateLock); + void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&); /* * VSYNC @@ -1339,9 +1338,7 @@ private: display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock); // The inner or outer display for foldables, assuming they have mutually exclusive power states. - // Atomic because writes from onActiveDisplayChangedLocked are not always under mStateLock, but - // reads from ISchedulerCallback::requestDisplayModes may happen concurrently. - std::atomic mActiveDisplayId GUARDED_BY(mStateLock); + std::atomic mActiveDisplayId; display::DisplayModeController mDisplayModeController; diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp index 5145e112d6..98d5754271 100644 --- a/services/surfaceflinger/tests/unittests/Android.bp +++ b/services/surfaceflinger/tests/unittests/Android.bp @@ -70,9 +70,9 @@ cc_test { "DisplayIdGeneratorTest.cpp", "DisplayTransactionTest.cpp", "DisplayDevice_GetBestColorModeTest.cpp", - "DisplayDevice_InitiateModeChange.cpp", "DisplayDevice_SetDisplayBrightnessTest.cpp", "DisplayDevice_SetProjectionTest.cpp", + "DisplayModeControllerTest.cpp", "EventThreadTest.cpp", "FlagManagerTest.cpp", "FpsReporterTest.cpp", diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp deleted file mode 100644 index c463a9271b..0000000000 --- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2021 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. - */ - -#undef LOG_TAG -#define LOG_TAG "LibSurfaceFlingerUnittests" - -#include "DisplayTransactionTestHelpers.h" -#include "mock/MockFrameRateMode.h" - -#include -#include - -#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt) \ - ASSERT_TRUE(requestOpt); \ - EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \ - EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent) - -namespace android { -namespace { - -using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; -using DisplayModeRequest = display::DisplayModeRequest; - -class InitiateModeChangeTest : public DisplayTransactionTest { -public: - using Action = DisplayDevice::DesiredModeAction; - void SetUp() override { - injectFakeBufferQueueFactory(); - injectFakeNativeWindowSurfaceFactory(); - - PrimaryDisplayVariant::setupHwcHotplugCallExpectations(this); - PrimaryDisplayVariant::setupFramebufferConsumerBufferQueueCallExpectations(this); - PrimaryDisplayVariant::setupFramebufferProducerBufferQueueCallExpectations(this); - PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this); - PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this); - - mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID, - DisplayHotplugEvent::CONNECTED); - mFlinger.configureAndCommit(); - - mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) - .setDisplayModes(makeModes(kMode60, kMode90, kMode120), kModeId60) - .inject(); - } - -protected: - sp mDisplay; - - static constexpr DisplayModeId kModeId60{0}; - static constexpr DisplayModeId kModeId90{1}; - static constexpr DisplayModeId kModeId120{2}; - - static inline const ftl::NonNull kMode60 = - ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz)); - static inline const ftl::NonNull kMode90 = - ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz)); - static inline const ftl::NonNull kMode120 = - ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz)); - - static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false}; - static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true}; - static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false}; - static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true}; -}; - -TEST_F(InitiateModeChangeTest, setDesiredModeToActiveMode) { - EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode60))); - EXPECT_FALSE(mDisplay->getDesiredMode()); -} - -TEST_F(InitiateModeChangeTest, setDesiredMode) { - EXPECT_EQ(Action::InitiateDisplayModeSwitch, - mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90))); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode()); - - EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120))); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode()); -} - -TEST_F(InitiateModeChangeTest, clearDesiredMode) { - EXPECT_EQ(Action::InitiateDisplayModeSwitch, - mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90))); - EXPECT_TRUE(mDisplay->getDesiredMode()); - - mDisplay->clearDesiredMode(); - EXPECT_FALSE(mDisplay->getDesiredMode()); -} - -TEST_F(InitiateModeChangeTest, initiateModeChange) REQUIRES(kMainThreadContext) { - EXPECT_EQ(Action::InitiateDisplayModeSwitch, - mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90))); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode()); - - const hal::VsyncPeriodChangeConstraints constraints{ - .desiredTimeNanos = systemTime(), - .seamlessRequired = false, - }; - hal::VsyncPeriodChangeTimeline timeline; - EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline)); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode()); - - mDisplay->clearDesiredMode(); - EXPECT_FALSE(mDisplay->getDesiredMode()); -} - -TEST_F(InitiateModeChangeTest, initiateRenderRateSwitch) { - EXPECT_EQ(Action::InitiateRenderRateSwitch, - mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode30))); - EXPECT_FALSE(mDisplay->getDesiredMode()); -} - -TEST_F(InitiateModeChangeTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) { - EXPECT_EQ(Action::InitiateDisplayModeSwitch, - mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90))); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode()); - - const hal::VsyncPeriodChangeConstraints constraints{ - .desiredTimeNanos = systemTime(), - .seamlessRequired = false, - }; - hal::VsyncPeriodChangeTimeline timeline; - EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline)); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode()); - - EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120))); - ASSERT_TRUE(mDisplay->getDesiredMode()); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode()); - - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode()); - - EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline)); - EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getPendingMode()); - - mDisplay->clearDesiredMode(); - EXPECT_FALSE(mDisplay->getDesiredMode()); -} - -} // namespace -} // namespace android diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp new file mode 100644 index 0000000000..d971150d42 --- /dev/null +++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp @@ -0,0 +1,234 @@ +/* + * Copyright 2024 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. + */ + +#undef LOG_TAG +#define LOG_TAG "LibSurfaceFlingerUnittests" + +#include "Display/DisplayModeController.h" +#include "Display/DisplaySnapshot.h" +#include "DisplayHardware/HWComposer.h" +#include "DisplayIdentificationTestHelpers.h" +#include "FpsOps.h" +#include "mock/DisplayHardware/MockComposer.h" +#include "mock/DisplayHardware/MockDisplayMode.h" +#include "mock/MockFrameRateMode.h" + +#include +#include +#include + +#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt) \ + ASSERT_TRUE(requestOpt); \ + EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \ + EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent) + +namespace android::display { +namespace { + +namespace hal = android::hardware::graphics::composer::hal; + +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; + +class DisplayModeControllerTest : public testing::Test { +public: + using Action = DisplayModeController::DesiredModeAction; + + void SetUp() override { + mDmc.setHwComposer(mComposer.get()); + mDmc.setActiveModeListener( + [this](PhysicalDisplayId displayId, Fps vsyncRate, Fps renderFps) { + mActiveModeListener.Call(displayId, vsyncRate, renderFps); + }); + + constexpr uint8_t kPort = 111; + EXPECT_CALL(*mComposerHal, getDisplayIdentificationData(kHwcDisplayId, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(kPort), SetArgPointee<2>(getInternalEdid()), + Return(hal::Error::NONE))); + + EXPECT_CALL(*mComposerHal, setClientTargetSlotCount(kHwcDisplayId)); + EXPECT_CALL(*mComposerHal, + setVsyncEnabled(kHwcDisplayId, hal::IComposerClient::Vsync::DISABLE)); + EXPECT_CALL(*mComposerHal, onHotplugConnect(kHwcDisplayId)); + + const auto infoOpt = mComposer->onHotplug(kHwcDisplayId, hal::Connection::CONNECTED); + ASSERT_TRUE(infoOpt); + + mDisplayId = infoOpt->id; + mDisplaySnapshotOpt.emplace(mDisplayId, ui::DisplayConnectionType::Internal, + makeModes(kMode60, kMode90, kMode120), ui::ColorModes{}, + std::nullopt); + + ftl::FakeGuard guard(kMainThreadContext); + mDmc.registerDisplay(*mDisplaySnapshotOpt, kModeId60, + scheduler::RefreshRateSelector::Config{}); + } + +protected: + hal::VsyncPeriodChangeConstraints expectModeSet(const DisplayModeRequest& request, + hal::VsyncPeriodChangeTimeline& timeline, + bool subsequent = false) { + EXPECT_CALL(*mComposerHal, + isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching)) + .WillOnce(Return(true)); + + if (!subsequent) { + EXPECT_CALL(*mComposerHal, getDisplayConnectionType(kHwcDisplayId, _)) + .WillOnce(DoAll(SetArgPointee<1>( + hal::IComposerClient::DisplayConnectionType::INTERNAL), + Return(hal::V2_4::Error::NONE))); + } + + const hal::VsyncPeriodChangeConstraints constraints{ + .desiredTimeNanos = systemTime(), + .seamlessRequired = false, + }; + + const hal::HWConfigId hwcModeId = request.mode.modePtr->getHwcId(); + + EXPECT_CALL(*mComposerHal, + setActiveConfigWithConstraints(kHwcDisplayId, hwcModeId, constraints, _)) + .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::V2_4::Error::NONE))); + + return constraints; + } + + static constexpr hal::HWDisplayId kHwcDisplayId = 1234; + + Hwc2::mock::Composer* mComposerHal = new testing::StrictMock(); + const std::unique_ptr mComposer{ + std::make_unique(std::unique_ptr(mComposerHal))}; + + testing::MockFunction mActiveModeListener; + + DisplayModeController mDmc; + + PhysicalDisplayId mDisplayId; + std::optional mDisplaySnapshotOpt; + + static constexpr DisplayModeId kModeId60{0}; + static constexpr DisplayModeId kModeId90{1}; + static constexpr DisplayModeId kModeId120{2}; + + static inline const ftl::NonNull kMode60 = + ftl::as_non_null(mock::createDisplayMode(kModeId60, 60_Hz)); + static inline const ftl::NonNull kMode90 = + ftl::as_non_null(mock::createDisplayMode(kModeId90, 90_Hz)); + static inline const ftl::NonNull kMode120 = + ftl::as_non_null(mock::createDisplayMode(kModeId120, 120_Hz)); + + static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false}; + static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true}; + static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false}; + static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true}; +}; + +TEST_F(DisplayModeControllerTest, setDesiredModeToActiveMode) { + EXPECT_CALL(mActiveModeListener, Call(_, _, _)).Times(0); + + EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode60))); + EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId)); +} + +TEST_F(DisplayModeControllerTest, setDesiredMode) { + // Called because setDesiredMode resets the render rate to the active refresh rate. + EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1); + + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90))); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId)); + + // No action since a mode switch has already been initiated. + EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode120))); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getDesiredMode(mDisplayId)); +} + +TEST_F(DisplayModeControllerTest, clearDesiredMode) { + // Called because setDesiredMode resets the render rate to the active refresh rate. + EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1); + + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90))); + EXPECT_TRUE(mDmc.getDesiredMode(mDisplayId)); + + mDmc.clearDesiredMode(mDisplayId); + EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId)); +} + +TEST_F(DisplayModeControllerTest, initiateModeChange) REQUIRES(kMainThreadContext) { + // Called because setDesiredMode resets the render rate to the active refresh rate. + EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1); + + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90))); + + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId)); + auto modeRequest = kDesiredMode90; + + hal::VsyncPeriodChangeTimeline timeline; + const auto constraints = expectModeSet(modeRequest, timeline); + + EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline)); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId)); + + mDmc.clearDesiredMode(mDisplayId); + EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId)); +} + +TEST_F(DisplayModeControllerTest, initiateRenderRateSwitch) { + EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 30_Hz)).Times(1); + + EXPECT_EQ(Action::InitiateRenderRateSwitch, + mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode30))); + EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId)); +} + +TEST_F(DisplayModeControllerTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) { + // Called because setDesiredMode resets the render rate to the active refresh rate. + EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1); + + EXPECT_EQ(Action::InitiateDisplayModeSwitch, + mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90))); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId)); + auto modeRequest = kDesiredMode90; + + hal::VsyncPeriodChangeTimeline timeline; + auto constraints = expectModeSet(modeRequest, timeline); + + EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline)); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId)); + + // No action since a mode switch has already been initiated. + EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode120))); + + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId)); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getDesiredMode(mDisplayId)); + modeRequest = kDesiredMode120; + + constexpr bool kSubsequent = true; + constraints = expectModeSet(modeRequest, timeline, kSubsequent); + + EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline)); + EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getPendingMode(mDisplayId)); + + mDmc.clearDesiredMode(mDisplayId); + EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId)); +} + +} // namespace +} // namespace android::display diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp index 078b4fe15e..0c3e875432 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp @@ -76,6 +76,7 @@ public: mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this) .setRefreshRateSelector(std::move(selectorPtr)) .inject(std::move(vsyncController), std::move(vsyncTracker)); + mDisplayId = mDisplay->getPhysicalId(); // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy // will call setActiveConfig instead of setActiveConfigWithConstraints. @@ -112,7 +113,11 @@ public: protected: void setupScheduler(std::shared_ptr); + auto& dmc() { return mFlinger.mutableDisplayModeController(); } + sp mDisplay, mOuterDisplay; + PhysicalDisplayId mDisplayId; + mock::EventThread* mAppEventThread; static constexpr DisplayModeId kModeId60{0}; @@ -167,17 +172,17 @@ void DisplayModeSwitchingTest::setupScheduler( TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) { ftl::FakeGuard guard(kMainThreadContext); - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90, false, 0, 120)); - ASSERT_TRUE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + ASSERT_TRUE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC const VsyncPeriodChangeTimeline timeline{.refreshRequired = true}; @@ -187,8 +192,8 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequ Mock::VerifyAndClearExpectations(mComposer); - EXPECT_TRUE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + EXPECT_TRUE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); // Verify that the next commit will complete the mode change and send // a onModeChanged event to the framework. @@ -198,23 +203,23 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequ mFlinger.commit(); Mock::VerifyAndClearExpectations(mAppEventThread); - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshRequired) { ftl::FakeGuard guard(kMainThreadContext); - EXPECT_FALSE(mDisplay->getDesiredMode()); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90, true, 0, 120)); - ASSERT_TRUE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + ASSERT_TRUE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. @@ -226,8 +231,8 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshR mFlinger.commit(); - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90); } TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { @@ -236,8 +241,8 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { // Test that if we call setDesiredDisplayModeSpecs while a previous mode change // is still being processed the later call will be respected. - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); @@ -252,46 +257,45 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) { mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId120, false, 0, 180)); - ASSERT_TRUE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120); + ASSERT_TRUE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120); EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120); mFlinger.commit(); - ASSERT_TRUE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120); + ASSERT_TRUE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId120); mFlinger.commit(); - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId120); } TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) { ftl::FakeGuard guard(kMainThreadContext); - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); mFlinger.onActiveDisplayChanged(nullptr, *mDisplay); mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120)); - ASSERT_TRUE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90_4K); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60); + ASSERT_TRUE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getDesiredMode(mDisplayId)->mode.modePtr->getId(), kModeId90_4K); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId60); // Verify that next commit will call setActiveConfigWithConstraints in HWC // and complete the mode change. const VsyncPeriodChangeTimeline timeline{.refreshRequired = false}; EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90_4K); - EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true)); + EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplayId, true)); - // Misc expecations. We don't need to enforce these method calls, but since the helper methods - // already set expectations we should add new ones here, otherwise the test will fail. + // Override expectations set up by PrimaryDisplayVariant. EXPECT_CALL(*mConsumer, setDefaultBufferSize(static_cast(kResolution4K.getWidth()), static_cast(kResolution4K.getHeight()))) @@ -304,31 +308,28 @@ TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRe injectFakeNativeWindowSurfaceFactory(); PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this); - const auto displayToken = mDisplay->getDisplayToken().promote(); - mFlinger.commit(); - // The DisplayDevice will be destroyed and recreated, - // so we need to update with the new instance. - mDisplay = mFlinger.getDisplay(displayToken); - - EXPECT_FALSE(mDisplay->getDesiredMode()); - EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K); + EXPECT_FALSE(dmc().getDesiredMode(mDisplayId)); + EXPECT_EQ(dmc().getActiveMode(mDisplayId).modePtr->getId(), kModeId90_4K); } MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") { - if (!arg->getDesiredMode()) { + const auto displayId = arg->getPhysicalId(); + auto& dmc = flinger->mutableDisplayModeController(); + + if (!dmc.getDesiredMode(displayId)) { *result_listener << "No desired mode"; return false; } - if (arg->getDesiredMode()->mode.modePtr->getId() != modeId) { + if (dmc.getDesiredMode(displayId)->mode.modePtr->getId() != modeId) { *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId); return false; } // VsyncModulator should react to mode switches on the pacesetter display. - if (arg->getPhysicalId() == flinger->scheduler()->pacesetterDisplayId() && + if (displayId == flinger->scheduler()->pacesetterDisplayId() && !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) { *result_listener << "VsyncModulator did not shift to early phase"; return false; @@ -337,8 +338,10 @@ MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") { return true; } -MATCHER_P(ModeSettledTo, modeId, "") { - if (const auto desiredOpt = arg->getDesiredMode()) { +MATCHER_P2(ModeSettledTo, dmc, modeId, "") { + const auto displayId = arg->getPhysicalId(); + + if (const auto desiredOpt = dmc->getDesiredMode(displayId)) { *result_listener << "Unsettled desired mode " << ftl::to_underlying(desiredOpt->mode.modePtr->getId()); return false; @@ -346,7 +349,7 @@ MATCHER_P(ModeSettledTo, modeId, "") { ftl::FakeGuard guard(kMainThreadContext); - if (arg->getActiveMode().modePtr->getId() != modeId) { + if (dmc->getActiveMode(displayId).modePtr->getId() != modeId) { *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId); return false; } @@ -367,14 +370,14 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { EXPECT_TRUE(innerDisplay->isPoweredOn()); EXPECT_FALSE(outerDisplay->isPoweredOn()); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF); mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), @@ -400,14 +403,14 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60)); mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF); mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60)); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), @@ -420,12 +423,12 @@ TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) { mFlinger.commit(); EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60)); mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60)); } TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { @@ -440,14 +443,14 @@ TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { EXPECT_TRUE(innerDisplay->isPoweredOn()); EXPECT_FALSE(outerDisplay->isPoweredOn()); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON); mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), @@ -473,13 +476,13 @@ TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) { mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60)); } TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { EXPECT_TRUE(mDisplay->isPoweredOn()); - EXPECT_THAT(mDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60)); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), @@ -502,7 +505,7 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) { mFlinger.commit(); - EXPECT_THAT(mDisplay, ModeSettledTo(kModeId90)); + EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90)); } TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { @@ -518,14 +521,14 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { EXPECT_TRUE(innerDisplay->isPoweredOn()); EXPECT_FALSE(outerDisplay->isPoweredOn()); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON); mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); EXPECT_EQ(NO_ERROR, mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(), @@ -555,8 +558,8 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60)); mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF); mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON); @@ -570,13 +573,13 @@ TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) { mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90)); EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId120)); mFlinger.commit(); - EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90)); - EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120)); + EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90)); + EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120)); } } // namespace diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp index 1bae5ffd30..933d03dac1 100644 --- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp +++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp @@ -232,7 +232,9 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { // Invocation DisplayDeviceState state; - if constexpr (constexpr auto connectionType = Case::Display::CONNECTION_TYPE::value) { + + constexpr auto kConnectionTypeOpt = Case::Display::CONNECTION_TYPE::value; + if constexpr (kConnectionTypeOpt) { const auto displayId = PhysicalDisplayId::tryCast(Case::Display::DISPLAY_ID::get()); ASSERT_TRUE(displayId); const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value; @@ -257,7 +259,7 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { const auto it = mFlinger.mutablePhysicalDisplays() .emplace_or_replace(*displayId, displayToken, *displayId, - *connectionType, makeModes(activeMode), + *kConnectionTypeOpt, makeModes(activeMode), std::move(colorModes), std::nullopt) .first; @@ -291,9 +293,12 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() { EXPECT_EQ(Case::Display::DISPLAY_FLAGS & DisplayDevice::eReceivesInput, device->receivesInput()); - if constexpr (Case::Display::CONNECTION_TYPE::value) { + if constexpr (kConnectionTypeOpt) { ftl::FakeGuard guard(kMainThreadContext); - EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().modePtr->getHwcId()); + EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, + mFlinger.mutableDisplayModeController() + .getActiveMode(device->getPhysicalId()) + .modePtr->getHwcId()); } } diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 8c72a7dba5..007383b3d9 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -191,6 +191,8 @@ public: void setupComposer(std::unique_ptr composer) { mFlinger->mCompositionEngine->setHwComposer( std::make_unique(std::move(composer))); + mFlinger->mDisplayModeController.setHwComposer( + &mFlinger->mCompositionEngine->getHwComposer()); } void setupPowerAdvisor(std::unique_ptr powerAdvisor) { @@ -1055,7 +1057,6 @@ public: auto& modes = mDisplayModes; auto& activeModeId = mActiveModeId; - std::optional refreshRateOpt; DisplayDeviceState state; state.isSecure = mCreationArgs.isSecure; @@ -1093,7 +1094,9 @@ public: const auto activeModeOpt = modes.get(activeModeId); LOG_ALWAYS_FATAL_IF(!activeModeOpt); - refreshRateOpt = activeModeOpt->get()->getPeakFps(); + + // Save a copy for use after `modes` is consumed. + const Fps refreshRate = activeModeOpt->get()->getPeakFps(); state.physical = {.id = *physicalId, .hwcDisplayId = *mHwcDisplayId, @@ -1109,6 +1112,9 @@ public: .registerDisplay(*physicalId, it->second.snapshot(), mCreationArgs.refreshRateSelector); + mFlinger.mutableDisplayModeController().setActiveMode(*physicalId, activeModeId, + refreshRate, refreshRate); + if (mFlinger.scheduler() && mSchedulerRegistration) { mFlinger.scheduler()->registerDisplay(*physicalId, mCreationArgs.refreshRateSelector, @@ -1120,10 +1126,6 @@ public: sp display = sp::make(mCreationArgs); mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display); - if (refreshRateOpt) { - display->setActiveMode(activeModeId, *refreshRateOpt, *refreshRateOpt); - } - mFlinger.mutableCurrentState().displays.add(mDisplayToken, state); mFlinger.mutableDrawingState().displays.add(mDisplayToken, state); -- GitLab From b4a6a771a17f401df890d8ebce0be67af92337fd Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 12 Jun 2024 14:41:08 -0700 Subject: [PATCH 431/465] Update snapshot changes flags when visibility changes Layerhistory walks through all snapshots and checks the change flags before updating its state. When a layer moves offscreen, we update its snapshot but did not update the change flag. This cl fixes this. Bug: 345021645 Test: presubmit Flag: EXEMPT bugfix Change-Id: Icee06279f0fa8b0f4c0539e99dde8960ba3fab31 --- .../surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp | 3 +++ .../tests/unittests/LayerSnapshotTest.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp index 6d4b0b5c54..ca53a0dfa6 100644 --- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp +++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp @@ -256,6 +256,9 @@ auto getBlendMode(const LayerSnapshot& snapshot, const RequestedLayerState& requ } void updateVisibility(LayerSnapshot& snapshot, bool visible) { + if (snapshot.isVisible != visible) { + snapshot.changes |= RequestedLayerState::Changes::Visibility; + } snapshot.isVisible = visible; // TODO(b/238781169) we are ignoring this compat for now, since we will have diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index bd0accd28e..dedb292b34 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -213,6 +213,17 @@ TEST_F(LayerSnapshotTest, childBehindParentCanBeHiddenByParent) { UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2}); } +TEST_F(LayerSnapshotTest, offscreenLayerSnapshotIsInvisible) { + EXPECT_EQ(getSnapshot(111)->isVisible, true); + + reparentLayer(11, UNASSIGNED_LAYER_ID); + destroyLayerHandle(11); + UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2}); + + EXPECT_EQ(getSnapshot(111)->isVisible, false); + EXPECT_TRUE(getSnapshot(111)->changes.test(RequestedLayerState::Changes::Visibility)); +} + // relative tests TEST_F(LayerSnapshotTest, RelativeParentCanHideChild) { reparentRelativeLayer(13, 11); -- GitLab From 2efbfb457349dd5cca1bdf9441aec08a5d727211 Mon Sep 17 00:00:00 2001 From: Aishwarya Mallampati Date: Mon, 10 Jun 2024 16:25:57 +0000 Subject: [PATCH 432/465] Add satellite feature flag. Bug: 337193821 Test: make Change-Id: I8b70f27e264a5776c07d0acd7e8d8a1baf839f90 --- .../android.hardware.telephony.satellite.xml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 data/etc/android.hardware.telephony.satellite.xml diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml new file mode 100644 index 0000000000..945e720392 --- /dev/null +++ b/data/etc/android.hardware.telephony.satellite.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file -- GitLab From dadc956b3660f6f26a8ac497ca7802d721eeb2fe Mon Sep 17 00:00:00 2001 From: Jiakai Zhang Date: Fri, 14 Jun 2024 11:24:04 +0100 Subject: [PATCH 433/465] Only enable Pre-reboot Dexopt if version >= 2. Version 2 contains a fix to make umount succeed. Bug: 346869873 Test: - 1. Turn on system tracing (Settings -> System -> Developer options -> System Tracing -> Record CPU profile). 2. system/update_engine/scripts/update_device.py (cherry picked from https://android-review.googlesource.com/q/commit:ad291ba21c37f91dd80abdb294436a9e6db8f018) Merged-In: Ie255217bb3a14648d94197020bb733d01f0733b4 Change-Id: Ie255217bb3a14648d94197020bb733d01f0733b4 --- cmds/installd/otapreopt_script.sh | 35 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh index 2d889fd80d..9384926e29 100644 --- a/cmds/installd/otapreopt_script.sh +++ b/cmds/installd/otapreopt_script.sh @@ -59,22 +59,27 @@ function infinite_source { done } -# Delegate to Pre-reboot Dexopt, a feature of ART Service. -# ART Service decides what to do with this request: -# - If Pre-reboot Dexopt is disabled or unsupported, the command returns -# non-zero. This is always the case if the current system is Android 14 or -# earlier. -# - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks -# until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds or -# not. This is the default behavior if the current system is Android 15. -# - If Pre-reboot Dexopt is enabled in asynchronous mode, the command schedules -# an asynchronous job and returns 0 immediately. The job will then run by the -# job scheduler when the device is idle and charging. -if infinite_source | pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then - # Handled by Pre-reboot Dexopt. - exit 0 +PR_DEXOPT_JOB_VERSION="$(pm art pr-dexopt-job --version)" +if (( $? == 0 )) && (( $PR_DEXOPT_JOB_VERSION >= 2 )); then + # Delegate to Pre-reboot Dexopt, a feature of ART Service. + # ART Service decides what to do with this request: + # - If Pre-reboot Dexopt is disabled or unsupported, the command returns + # non-zero. This is always the case if the current system is Android 14 or + # earlier. + # - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks + # until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds or + # not. This is the default behavior if the current system is Android 15. + # - If Pre-reboot Dexopt is enabled in asynchronous mode, the command schedules + # an asynchronous job and returns 0 immediately. The job will then run by the + # job scheduler when the device is idle and charging. + if infinite_source | pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then + # Handled by Pre-reboot Dexopt. + exit 0 + fi + echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt." +else + echo "Pre-reboot Dexopt is too old. Fall back to otapreopt." fi -echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt." if [ "$(/system/bin/otapreopt_chroot --version)" != 2 ]; then # We require an updated chroot wrapper that reads dexopt commands from stdin. -- GitLab From ab9428635fa948bcfbc37199372532dde8e010bc Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Thu, 13 Jun 2024 22:59:06 -0700 Subject: [PATCH 434/465] Trigger windowinfo updates when alpha changes Input and TrustedPresentationListener logic relies on layer alpha. This change ensures that SurfaceFlinger sends the updated window info when only the alpha changes. Fixes: 325254898 Test: presubmit Flag: EXEMPT bugfix Change-Id: I6b9a5b2874add278245cb682c5362c18403c70b0 --- libs/gui/include/gui/LayerState.h | 6 +++--- .../tests/unittests/LayerLifecycleManagerTest.cpp | 6 ++++-- .../tests/unittests/LayerSnapshotTest.cpp | 10 ++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h index 82889efe36..9adf9193d3 100644 --- a/libs/gui/include/gui/LayerState.h +++ b/libs/gui/include/gui/LayerState.h @@ -276,9 +276,9 @@ struct layer_state_t { layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged; // Changes affecting data sent to input. - static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged | - layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged | - layer_state_t::eLayerStackChanged; + static constexpr uint64_t INPUT_CHANGES = layer_state_t::eAlphaChanged | + layer_state_t::eInputInfoChanged | layer_state_t::eDropInputModeChanged | + layer_state_t::eTrustedOverlayChanged | layer_state_t::eLayerStackChanged; // Changes that affect the visible region on a display. static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES | diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp index 97bd79fcee..bc15dec493 100644 --- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp @@ -538,7 +538,8 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags( RequestedLayerState::Changes::Content | RequestedLayerState::Changes::AffectsChildren | - RequestedLayerState::Changes::VisibleRegion) + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Input) .string()); EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast(startingAlpha)); mLifecycleManager.commitChanges(); @@ -551,7 +552,8 @@ TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) { ftl::Flags( RequestedLayerState::Changes::Content | RequestedLayerState::Changes::AffectsChildren | - RequestedLayerState::Changes::VisibleRegion) + RequestedLayerState::Changes::VisibleRegion | + RequestedLayerState::Changes::Input) .string()); EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast(endingAlpha)); mLifecycleManager.commitChanges(); diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp index bd0accd28e..8cf3f035ea 100644 --- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp +++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp @@ -1161,6 +1161,16 @@ TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) { gui::WindowInfo::InputConfig::TRUSTED_OVERLAY)); } +TEST_F(LayerSnapshotTest, alphaChangesPropagateToInput) { + Region touch{Rect{0, 0, 1000, 1000}}; + setTouchableRegion(1, touch); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + + setAlpha(1, 0.5f); + UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER); + EXPECT_EQ(getSnapshot(1)->inputInfo.alpha, 0.5f); +} + TEST_F(LayerSnapshotTest, isFrontBuffered) { setBuffer(1, std::make_shared( -- GitLab From 1d0cae97cd67a455563a92f1f11086dd1f030877 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Fri, 14 Jun 2024 13:41:12 -0700 Subject: [PATCH 435/465] SF: trace the render rate of an app Add a trace point for both SF and apps when the render rate is overridden. Bug: 347314033 Test: manual Change-Id: I87dc9b17a4c1f97fa43b025cfa684f43c20c5a25 --- libs/gui/Android.bp | 5 +++++ libs/gui/DisplayEventDispatcher.cpp | 13 ++++++++++++- libs/gui/libgui_flags.aconfig | 17 ++++++++++++++--- .../Scheduler/RefreshRateSelector.cpp | 5 +++++ services/surfaceflinger/common/Android.bp | 4 ++++ services/surfaceflinger/common/FlagManager.cpp | 4 ++++ .../common/include/common/FlagManager.h | 1 + 7 files changed, 45 insertions(+), 4 deletions(-) diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp index 2547297ff8..9ef0eacfce 100644 --- a/libs/gui/Android.bp +++ b/libs/gui/Android.bp @@ -41,6 +41,11 @@ cc_aconfig_library { aconfig_declarations: "libgui_flags", } +cc_aconfig_library { + name: "libguiflags_no_apex", + aconfig_declarations: "libgui_flags", +} + cc_library_headers { name: "libgui_headers", vendor_available: true, diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp index f3de96d2cd..c46f9c50ef 100644 --- a/libs/gui/DisplayEventDispatcher.cpp +++ b/libs/gui/DisplayEventDispatcher.cpp @@ -15,6 +15,7 @@ */ #define LOG_TAG "DisplayEventDispatcher" +#define ATRACE_TAG ATRACE_TAG_GRAPHICS #include #include @@ -23,10 +24,13 @@ #include #include #include - #include +#include + +#include namespace android { +using namespace com::android::graphics::libgui; // Number of events to read at a time from the DisplayEventDispatcher pipe. // The value should be large enough that we can quickly drain the pipe @@ -171,6 +175,13 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp, *outDisplayId = ev.header.displayId; *outCount = ev.vsync.count; *outVsyncEventData = ev.vsync.vsyncData; + + // Trace the RenderRate for this app + if (ATRACE_ENABLED() && flags::trace_frame_rate_override()) { + const auto frameInterval = ev.vsync.vsyncData.frameInterval; + int fps = frameInterval > 0 ? 1e9f / frameInterval : 0; + ATRACE_INT("RenderRate", fps); + } break; case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: if (ev.hotplug.connectionError == 0) { diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig index a902a8c5cb..792966fd5e 100644 --- a/libs/gui/libgui_flags.aconfig +++ b/libs/gui/libgui_flags.aconfig @@ -7,7 +7,7 @@ flag { description: "This flag controls plumbing setFrameRate thru BufferQueue" bug: "281695725" is_fixed_read_only: true -} +} # bq_setframerate flag { name: "frametimestamps_previousrelease" @@ -15,7 +15,7 @@ flag { description: "Controls a fence fixup for timestamp apis" bug: "310927247" is_fixed_read_only: true -} +} # frametimestamps_previousrelease flag { name: "bq_extendedallocate" @@ -23,4 +23,15 @@ flag { description: "Add BQ support for allocate with extended options" bug: "268382490" is_fixed_read_only: true -} +} # bq_extendedallocate + +flag { + name: "trace_frame_rate_override" + namespace: "core_graphics" + description: "Trace FrameRateOverride fps" + bug: "347314033" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} # trace_frame_rate_override diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 2c664928ee..846727b1d5 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -1066,6 +1066,11 @@ auto RefreshRateSelector::getFrameRateOverrides(const std::vector #include +#include #include #include @@ -151,6 +152,7 @@ void FlagManager::dump(std::string& result) const { DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache); DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine); DUMP_READ_ONLY_FLAG(single_hop_screenshot); + DUMP_READ_ONLY_FLAG(trace_frame_rate_override); #undef DUMP_READ_ONLY_FLAG #undef DUMP_SERVER_FLAG @@ -264,5 +266,7 @@ FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os) FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "", com::android::server::display::feature::flags) FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os) +FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(trace_frame_rate_override, "", + com::android::graphics::libgui::flags); } // namespace android diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h index 22118ab585..ab7a474d2c 100644 --- a/services/surfaceflinger/common/include/common/FlagManager.h +++ b/services/surfaceflinger/common/include/common/FlagManager.h @@ -90,6 +90,7 @@ public: bool flush_buffer_slots_to_uncache() const; bool force_compile_graphite_renderengine() const; bool single_hop_screenshot() const; + bool trace_frame_rate_override() const; protected: // overridden for unit tests -- GitLab From 33cfc6d2e2ef7f12e7abef6977d67cb42b172c30 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 11 Jun 2024 20:17:44 +0000 Subject: [PATCH 436/465] InputDispatcher: Fix pointer count when canceling a subset of pointers There was a bug in the logic for calculating the pointer coords and properties for canceling a subset of pointers. It was introduced when we refactored storing the coords in a vector rather than using a fixed-length array with a pointerCount variable in the following change: I91b4a88ec5df3f017ade8a6e55be3d3c1281de75 Bug: 346342507 Test: atest inputflinger_tests Flag: NONE bug fix Change-Id: Ie9a5fbd5ba1fd75c53b7289a93573ec2d0d1947b --- .../inputflinger/dispatcher/InputState.cpp | 74 ++++++++----------- services/inputflinger/tests/FakeWindows.h | 10 +-- .../tests/InputDispatcher_test.cpp | 32 +++++--- 3 files changed, 54 insertions(+), 62 deletions(-) diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp index 46e7e8b35e..dfbe02f332 100644 --- a/services/inputflinger/dispatcher/InputState.cpp +++ b/services/inputflinger/dispatcher/InputState.cpp @@ -499,67 +499,53 @@ std::vector> InputState::synthesizeCancelationEvent nsecs_t currentTime) { std::vector> events; std::vector canceledPointerIndices; - std::vector pointerProperties(MAX_POINTERS); - std::vector pointerCoords(MAX_POINTERS); + for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) { uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id); - pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx]; - pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx]; if (pointerIds.test(pointerId)) { canceledPointerIndices.push_back(pointerIdx); } } if (canceledPointerIndices.size() == memento.getPointerCount()) { - const int32_t action = - memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL; - int32_t flags = memento.flags; - if (action == AMOTION_EVENT_ACTION_CANCEL) { - flags |= AMOTION_EVENT_FLAG_CANCELED; + // We are cancelling all pointers. + events.emplace_back(createCancelEntryForMemento(memento, currentTime)); + return events; + } + + // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with + // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the + // previously canceled pointers from PointerProperties and PointerCoords, and update + // pointerCount appropriately. For convenience, sort the canceled pointer indices in + // descending order so that we can just slide the remaining pointers to the beginning of + // the array when a pointer is canceled. + std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(), + std::greater()); + + std::vector pointerProperties = memento.pointerProperties; + std::vector pointerCoords = memento.pointerCoords; + for (const uint32_t pointerIdx : canceledPointerIndices) { + if (pointerProperties.size() <= 1) { + LOG(FATAL) << "Unexpected code path for canceling all pointers!"; } + const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | + (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); events.push_back( std::make_unique(mIdGenerator.nextId(), /*injectionState=*/nullptr, currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, - /*actionButton=*/0, flags, AMETA_NONE, - /*buttonState=*/0, MotionClassification::NONE, + /*actionButton=*/0, + memento.flags | AMOTION_EVENT_FLAG_CANCELED, + AMETA_NONE, /*buttonState=*/0, + MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, memento.yCursorPosition, memento.downTime, - memento.pointerProperties, memento.pointerCoords)); - } else { - // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with - // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the - // previously canceled pointers from PointerProperties and PointerCoords, and update - // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we - // can just slide the remaining pointers to the beginning of the array when a pointer is - // canceled. - std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(), - std::greater()); - - uint32_t pointerCount = memento.getPointerCount(); - for (const uint32_t pointerIdx : canceledPointerIndices) { - const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL - : AMOTION_EVENT_ACTION_POINTER_UP | - (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - events.push_back( - std::make_unique(mIdGenerator.nextId(), /*injectionState=*/nullptr, - currentTime, memento.deviceId, memento.source, - memento.displayId, memento.policyFlags, action, - /*actionButton=*/0, - memento.flags | AMOTION_EVENT_FLAG_CANCELED, - AMETA_NONE, /*buttonState=*/0, - MotionClassification::NONE, - AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, - memento.yPrecision, memento.xCursorPosition, - memento.yCursorPosition, memento.downTime, - pointerProperties, pointerCoords)); + pointerProperties, pointerCoords)); - // Cleanup pointer information - pointerProperties.erase(pointerProperties.begin() + pointerIdx); - pointerCoords.erase(pointerCoords.begin() + pointerIdx); - pointerCount--; - } + // Cleanup pointer information + pointerProperties.erase(pointerProperties.begin() + pointerIdx); + pointerCoords.erase(pointerCoords.begin() + pointerIdx); } return events; } diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index 36a8f0066e..ee65d3a375 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -304,15 +304,11 @@ public: WithFlags(expectedFlags))); } - inline void consumeMotionPointerUp( - int32_t pointerIdx, - ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT, - int32_t expectedFlags = 0) { + inline void consumeMotionPointerUp(int32_t pointerIdx, + const ::testing::Matcher& matcher) { const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); - consumeMotionEvent(testing::AllOf(WithMotionAction(action), - WithDisplayId(expectedDisplayId), - WithFlags(expectedFlags))); + consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher)); } inline void consumeMotionUp( diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 56a05a3a3c..244deab767 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -1282,9 +1282,11 @@ TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) { injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT)) << "Inject motion event should return InputEventInjectionResult::SUCCEEDED"; - foregroundWindow->consumeMotionPointerUp(0); - wallpaperWindow->consumeMotionPointerUp(0, ui::LogicalDisplayId::DEFAULT, - EXPECTED_WALLPAPER_FLAGS); + foregroundWindow->consumeMotionPointerUp(/*pointerIdx=*/0, + WithDisplayId(ui::LogicalDisplayId::DEFAULT)); + wallpaperWindow->consumeMotionPointerUp(/*pointerIdx=*/0, + AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT), + WithFlags(EXPECTED_WALLPAPER_FLAGS))); ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, @@ -6220,8 +6222,10 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) { {touchPoint, touchPoint})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); - secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT, - AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionPointerUp(/*pointerIdx=*/1, + AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT), + WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE), + WithPointerCount(2))); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, @@ -6363,8 +6367,10 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) { {pointInFirst, pointInSecond})); // The first window gets nothing and the second gets pointer up firstWindow->assertNoEvents(); - secondWindow->consumeMotionPointerUp(1, ui::LogicalDisplayId::DEFAULT, - AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE); + secondWindow->consumeMotionPointerUp(/*pointerIdx=*/1, + AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT), + WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE), + WithPointerCount(2))); // Send up event to the second window mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, @@ -12878,10 +12884,14 @@ TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) { // Spy window pilfers the pointers. EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken())); - window->consumeMotionPointerUp(/*idx=*/2, ui::LogicalDisplayId::DEFAULT, - AMOTION_EVENT_FLAG_CANCELED); - window->consumeMotionPointerUp(/*idx=*/1, ui::LogicalDisplayId::DEFAULT, - AMOTION_EVENT_FLAG_CANCELED); + window->consumeMotionPointerUp(/*pointerIdx=*/2, + AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT), + WithFlags(AMOTION_EVENT_FLAG_CANCELED), + WithPointerCount(3))); + window->consumeMotionPointerUp(/*pointerIdx=*/1, + AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT), + WithFlags(AMOTION_EVENT_FLAG_CANCELED), + WithPointerCount(2))); spy->assertNoEvents(); window->assertNoEvents(); -- GitLab From 4fc32e0452301aeb2844e4df73523bdb5ed23ccf Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Fri, 14 Jun 2024 16:00:58 +0000 Subject: [PATCH 437/465] InputTracer: Add timestamp to perfetto trace packets Use the event processing time as the packet timestamp for input events, and use the delivery time as the packet timestamp for dispatch events. Bug: 332714237 Bug: 210460522 Test: atest inputflinger_tests Test: manual with perfetto Change-Id: I30f4c21dfee68603d0dad01d205f8a5581e6db31 --- services/inputflinger/dispatcher/InputDispatcher.cpp | 9 +++++---- services/inputflinger/dispatcher/trace/InputTracer.cpp | 10 ++++++---- services/inputflinger/dispatcher/trace/InputTracer.h | 5 +++-- .../dispatcher/trace/InputTracerInterface.h | 3 ++- .../dispatcher/trace/InputTracingBackendInterface.h | 2 ++ .../dispatcher/trace/InputTracingPerfettoBackend.cpp | 6 ++++++ services/inputflinger/tests/InputTraceSession.cpp | 5 +++++ 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index 5ed5eb8e33..347f106686 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -879,7 +879,7 @@ std::pair expandCancellatio class ScopedSyntheticEventTracer { public: ScopedSyntheticEventTracer(std::unique_ptr& tracer) - : mTracer(tracer) { + : mTracer(tracer), mProcessingTimestamp(now()) { if (mTracer) { mEventTracker = mTracer->createTrackerForSyntheticEvent(); } @@ -887,7 +887,7 @@ public: ~ScopedSyntheticEventTracer() { if (mTracer) { - mTracer->eventProcessingComplete(*mEventTracker); + mTracer->eventProcessingComplete(*mEventTracker, mProcessingTimestamp); } } @@ -896,8 +896,9 @@ public: } private: - std::unique_ptr& mTracer; + const std::unique_ptr& mTracer; std::unique_ptr mEventTracker; + const nsecs_t mProcessingTimestamp; }; } // namespace @@ -1263,7 +1264,7 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t& nextWakeupTime) { if (mTracer) { if (auto& traceTracker = getTraceTracker(*mPendingEvent); traceTracker != nullptr) { - mTracer->eventProcessingComplete(*traceTracker); + mTracer->eventProcessingComplete(*traceTracker, currentTime); } } diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp index a1a87afd04..5d2b8541d1 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp @@ -145,7 +145,8 @@ void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie, eventState->metadata.isSecure |= targetInfo.isSecureWindow; } -void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { +void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie, + nsecs_t processingTimestamp) { if (isDerivedCookie(cookie)) { LOG(FATAL) << "Event processing cannot be set from a derived cookie."; } @@ -154,7 +155,7 @@ void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) { LOG(FATAL) << "Traced event was already logged. " "eventProcessingComplete() was likely called more than once."; } - eventState->onEventProcessingComplete(); + eventState->onEventProcessingComplete(processingTimestamp); } std::unique_ptr InputTracer::traceDerivedEvent( @@ -242,7 +243,8 @@ bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) { // --- InputTracer::EventState --- -void InputTracer::EventState::onEventProcessingComplete() { +void InputTracer::EventState::onEventProcessingComplete(nsecs_t processingTimestamp) { + metadata.processingTimestamp = processingTimestamp; metadata.isImeConnectionActive = tracer.mIsImeConnectionActive; // Write all of the events known so far to the trace. @@ -277,7 +279,7 @@ InputTracer::EventState::~EventState() { // We should never end up here in normal operation. However, in tests, it's possible that we // stop and destroy InputDispatcher without waiting for it to finish processing events, at // which point an event (and thus its EventState) may be destroyed before processing finishes. - onEventProcessingComplete(); + onEventProcessingComplete(systemTime(CLOCK_MONOTONIC)); } } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index ab175bef4a..cb525a4a92 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -44,7 +44,8 @@ public: std::unique_ptr traceInboundEvent(const EventEntry&) override; std::unique_ptr createTrackerForSyntheticEvent() override; void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override; - void eventProcessingComplete(const EventTrackerInterface&) override; + void eventProcessingComplete(const EventTrackerInterface&, + nsecs_t processingTimestamp) override; std::unique_ptr traceDerivedEvent(const EventEntry&, const EventTrackerInterface&) override; void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override; @@ -61,7 +62,7 @@ private: explicit inline EventState(InputTracer& tracer) : tracer(tracer){}; ~EventState(); - void onEventProcessingComplete(); + void onEventProcessingComplete(nsecs_t processingTimestamp); InputTracer& tracer; std::vector events; diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h index af6eefb1db..f5e4e592bb 100644 --- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h @@ -81,7 +81,8 @@ public: * outside of our control, such as how long apps take to respond, so we don't want to depend on * that. */ - virtual void eventProcessingComplete(const EventTrackerInterface&) = 0; + virtual void eventProcessingComplete(const EventTrackerInterface&, + nsecs_t processingTimestamp) = 0; /** * Trace an input event that is derived from another event. This is used in cases where an event diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h index 25099c3199..761d619cec 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h +++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h @@ -99,6 +99,8 @@ struct TracedEventMetadata { std::set targets; // True if the there was an active input method connection while this event was processed. bool isImeConnectionActive; + // The timestamp for when the dispatching decisions were made for the event by the system. + nsecs_t processingTimestamp; }; /** Additional information about an input event being dispatched to a window. */ diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 3d30ad6c7b..77b5c2ebcd 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -231,6 +231,8 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); + tracePacket->set_timestamp(metadata.processingTimestamp); + tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC); auto* winscopeExtensions = static_cast( tracePacket->set_winscope_extensions()); auto* inputEvent = winscopeExtensions->set_android_input_event(); @@ -257,6 +259,8 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); + tracePacket->set_timestamp(metadata.processingTimestamp); + tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC); auto* winscopeExtensions = static_cast( tracePacket->set_winscope_extensions()); auto* inputEvent = winscopeExtensions->set_android_input_event(); @@ -283,6 +287,8 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs } const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED; auto tracePacket = ctx.NewTracePacket(); + tracePacket->set_timestamp(dispatchArgs.deliveryTime); + tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC); auto* winscopeExtensions = static_cast( tracePacket->set_winscope_extensions()); auto* inputEvent = winscopeExtensions->set_android_input_event(); diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp index a9d370aedd..db4e761162 100644 --- a/services/inputflinger/tests/InputTraceSession.cpp +++ b/services/inputflinger/tests/InputTraceSession.cpp @@ -103,6 +103,11 @@ auto decodeTrace(const std::string& rawTrace) { continue; } + EXPECT_TRUE(packet.has_timestamp()); + EXPECT_TRUE(packet.has_timestamp_clock_id()); + EXPECT_EQ(packet.timestamp_clock_id(), + static_cast(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC)); + AndroidInputEvent::Decoder event{field.as_bytes()}; if (event.has_dispatcher_motion_event()) { tracedMotions.emplace_back(event.dispatcher_motion_event(), -- GitLab From 575aa7232035b8da3336ee5170e8016bd16460ae Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Mon, 17 Jun 2024 11:03:30 -0400 Subject: [PATCH 438/465] SF: Fix pacesetter promotion for folded mirroring The pacesetter display is demoted/promoted in response to a hotplug. On foldables, the promoted display was hard-coded to the inner display, so a hotplug while folded would incorrectly use that powered-off display as the pacesetter, causing system-wide jank until the next fold/unfold. Fixes: 347248313 Flag: EXEMPT bugfix Test: Connect and disconnect external display while folded. Test: Fold and unfold while external display is connected. Test: Pacesetter is still correct on folded/unfolded boot. (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3ff44c7e852f5614514a15dcec78ba7857e5d010) Merged-In: Id5cb29c3cbaa8ed455a15d8be3a32e79a470cce5 Change-Id: Id5cb29c3cbaa8ed455a15d8be3a32e79a470cce5 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 17 ++++++++----- services/surfaceflinger/Scheduler/Scheduler.h | 16 +++++++++--- services/surfaceflinger/SurfaceFlinger.cpp | 8 +++--- .../tests/unittests/SchedulerTest.cpp | 25 ++++++++++++------- .../tests/unittests/TestableScheduler.h | 16 ++++++------ .../tests/unittests/TestableSurfaceFlinger.h | 4 +-- 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 60681a2bc8..26e11e5693 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -123,19 +123,22 @@ void Scheduler::setPacesetterDisplay(std::optional pacesetter promotePacesetterDisplay(pacesetterIdOpt); } -void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) { +void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, + PhysicalDisplayId activeDisplayId) { auto schedulePtr = std::make_shared(selectorPtr->getActiveMode().modePtr, mFeatures, [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); }); - registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr)); + registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr), + activeDisplayId); } void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, - VsyncSchedulePtr schedulePtr) { + VsyncSchedulePtr schedulePtr, + PhysicalDisplayId activeDisplayId) { demotePacesetterDisplay(); auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) { @@ -145,7 +148,7 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, std::move(schedulePtr), mFeatures) .second; - return std::make_pair(promotePacesetterDisplayLocked(), isNew); + return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId), isNew); }(); applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); @@ -158,7 +161,9 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, dispatchHotplug(displayId, Hotplug::Connected); } -void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { +void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId activeDisplayId) { + LOG_ALWAYS_FATAL_IF(displayId == activeDisplayId, "Cannot unregister the active display!"); + dispatchHotplug(displayId, Hotplug::Disconnected); demotePacesetterDisplay(); @@ -173,7 +178,7 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) { // headless virtual display.) LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!"); - pacesetterVsyncSchedule = promotePacesetterDisplayLocked(); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId); } applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index ccaa05f970..1a4aa79818 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -101,9 +101,16 @@ public: using ConstVsyncSchedulePtr = std::shared_ptr; using VsyncSchedulePtr = std::shared_ptr; - void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext) + // After registration/unregistration, `activeDisplayId` is promoted to pacesetter. Note that the + // active display is never unregistered, since hotplug disconnect never happens for activatable + // displays, i.e. a foldable's internal displays or otherwise the (internal or external) primary + // display. + // TODO: b/255635821 - Remove active display parameters. + void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr, + PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); - void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + void unregisterDisplay(PhysicalDisplayId, PhysicalDisplayId activeDisplayId) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); void run(); @@ -390,8 +397,9 @@ private: // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); - void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr) - REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr, + PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); struct Policy; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 596ec12d9e..5fa6d26dfe 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -3855,7 +3855,8 @@ void SurfaceFlinger::processDisplayAdded(const wp& displayToken, ftl::FakeGuard guard(kMainThreadContext); // For hotplug reconnect, renew the registration since display modes have been reloaded. - mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); + mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(), + mActiveDisplayId); } if (display->isVirtual()) { @@ -3894,7 +3895,7 @@ void SurfaceFlinger::processDisplayRemoved(const wp& displayToken) { if (display->isVirtual()) { releaseVirtualDisplay(display->getVirtualId()); } else { - mScheduler->unregisterDisplay(display->getPhysicalId()); + mScheduler->unregisterDisplay(display->getPhysicalId(), mActiveDisplayId); } } @@ -4506,7 +4507,8 @@ void SurfaceFlinger::initScheduler(const sp& display) { getFactory(), activeRefreshRate, *mTimeStats); // The pacesetter must be registered before EventThread creation below. - mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector()); + mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(), + mActiveDisplayId); if (FlagManager::getInstance().vrr_config()) { mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps, /*applyImmediately*/ true); diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp index 4fb06907d0..fc54a8b74f 100644 --- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp +++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp @@ -343,12 +343,15 @@ TEST_F(SchedulerTest, chooseDisplayModesSingleDisplayHighHintTouchSignal) { } TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { + constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1; mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, - kDisplay1Mode60->getId())); + kDisplay1Mode60->getId()), + kActiveDisplayId); mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, - kDisplay2Mode60->getId())); + kDisplay2Mode60->getId()), + kActiveDisplayId); mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON); mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON); @@ -411,10 +414,10 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { { // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120 // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz. - mScheduler - ->registerDisplay(kDisplayId3, - std::make_shared(kDisplay3Modes, - kDisplay3Mode60->getId())); + mScheduler->registerDisplay(kDisplayId3, + std::make_shared(kDisplay3Modes, + kDisplay3Mode60->getId()), + kActiveDisplayId); mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON); const GlobalSignals globalSignals = {.touch = true}; @@ -457,12 +460,15 @@ TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) { } TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) { + constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1; mScheduler->registerDisplay(kDisplayId1, std::make_shared(kDisplay1Modes, - kDisplay1Mode60->getId())); + kDisplay1Mode60->getId()), + kActiveDisplayId); mScheduler->registerDisplay(kDisplayId2, std::make_shared(kDisplay2Modes, - kDisplay2Mode60->getId())); + kDisplay2Mode60->getId()), + kActiveDisplayId); using VsyncIds = std::vector>; @@ -585,7 +591,8 @@ TEST_F(SchedulerTest, nextFrameIntervalTest) { mFlinger.getTimeStats(), mSchedulerCallback}; - scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker); + scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, std::nullopt, + vrrTracker); vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate); scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false); vrrTracker->addVsyncTimestamp(0); diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h index 198a5def8c..f0638094c7 100644 --- a/services/surfaceflinger/tests/unittests/TestableScheduler.h +++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h @@ -53,7 +53,7 @@ public: factory, selectorPtr->getActiveMode().fps, timeStats) { const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId(); registerDisplay(displayId, std::move(selectorPtr), std::move(controller), - std::move(tracker)); + std::move(tracker), displayId); ON_CALL(*this, postMessage).WillByDefault([](sp&& handler) { // Execute task to prevent broken promise exception on destruction. @@ -85,14 +85,16 @@ public: void registerDisplay( PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, + std::optional activeDisplayIdOpt = {}, std::shared_ptr vsyncTracker = std::make_shared()) { registerDisplay(displayId, std::move(selectorPtr), - std::make_unique(), vsyncTracker); + std::make_unique(), vsyncTracker, + activeDisplayIdOpt.value_or(displayId)); } void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, std::unique_ptr controller, - std::shared_ptr tracker) { + std::shared_ptr tracker, PhysicalDisplayId activeDisplayId) { ftl::FakeGuard guard(kMainThreadContext); Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr), std::shared_ptr( @@ -101,16 +103,12 @@ public: mock::VSyncDispatch>(), std::move(controller), mockRequestHardwareVsync - .AsStdFunction()))); + .AsStdFunction())), + activeDisplayId); } testing::MockFunction mockRequestHardwareVsync; - void unregisterDisplay(PhysicalDisplayId displayId) { - ftl::FakeGuard guard(kMainThreadContext); - Scheduler::unregisterDisplay(displayId); - } - void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) { ftl::FakeGuard guard(kMainThreadContext); Scheduler::setDisplayPowerMode(displayId, powerMode); diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h index 007383b3d9..4197cbd271 100644 --- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h +++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h @@ -1118,8 +1118,8 @@ public: if (mFlinger.scheduler() && mSchedulerRegistration) { mFlinger.scheduler()->registerDisplay(*physicalId, mCreationArgs.refreshRateSelector, - std::move(controller), - std::move(tracker)); + std::move(controller), std::move(tracker), + mFlinger.mutableActiveDisplayId()); } } -- GitLab From 277cbafb4069e260b29bd2b039255b2d5f699ef7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 24 Jun 2024 10:44:13 -0400 Subject: [PATCH 439/465] Fix lambda in presentFrameAndReleaseLayersAsync Follow on to I7baa3e76af86329fb266395e63e92a0ba38967f4, which made a lambda use a variable it did not capture correctly. Fix that capture. Bug: 330806421 Bug: 347659752 Test: manual Flag: EXEMPT bug fix (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:1972557dca9614dfd1c32fe34dd901d9edbf7fa9) Merged-In: I3a8dd1670113111e01de3471b8061ff24dd0a115 Change-Id: I3a8dd1670113111e01de3471b8061ff24dd0a115 --- services/surfaceflinger/CompositionEngine/src/Output.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp index 5b9a10252e..b40aea4210 100644 --- a/services/surfaceflinger/CompositionEngine/src/Output.cpp +++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp @@ -1102,7 +1102,7 @@ void Output::prepareFrame() { } ftl::Future Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) { - return ftl::Future(std::move(mHwComposerAsyncWorker->send([&]() { + return ftl::Future(std::move(mHwComposerAsyncWorker->send([this, flushEvenWhenDisabled]() { presentFrameAndReleaseLayers(flushEvenWhenDisabled); return true; }))) -- GitLab From 96a5aaf57b4432cb3d5cb4a2327ab548b262354b Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Wed, 26 Jun 2024 17:17:51 -0700 Subject: [PATCH 440/465] Fix region sampling for secure layers We were swapping a couple of boolean params when requesting screenshots for region sampling, breaking region sampling for secure layers in the process. Fix this by passing flags instead of booleans in a long list of arguments and add a test to verify the code path. FLAG: EXEMPT bugfix Fixes: 348944802 Test: presubmit (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:871886eebe469fc21568ff363993bde9a6593837) Merged-In: I58f12e5cf81cb59115c1b9c500ac1e18a8ec72e5 Change-Id: I58f12e5cf81cb59115c1b9c500ac1e18a8ec72e5 --- libs/gui/tests/RegionSampling_test.cpp | 58 ++++++++++++++----- services/surfaceflinger/DisplayRenderArea.cpp | 16 +++-- services/surfaceflinger/DisplayRenderArea.h | 5 +- services/surfaceflinger/LayerRenderArea.cpp | 9 ++- services/surfaceflinger/LayerRenderArea.h | 4 +- .../surfaceflinger/RegionSamplingThread.cpp | 6 +- services/surfaceflinger/RenderArea.h | 24 +++++--- services/surfaceflinger/RenderAreaBuilder.h | 40 ++++--------- services/surfaceflinger/SurfaceFlinger.cpp | 24 +++++--- .../tests/unittests/CompositionTest.cpp | 8 ++- 10 files changed, 111 insertions(+), 83 deletions(-) diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp index b18b544257..223e4b6cbd 100644 --- a/libs/gui/tests/RegionSampling_test.cpp +++ b/libs/gui/tests/RegionSampling_test.cpp @@ -239,8 +239,9 @@ protected: float const luma_green = 0.7152; uint32_t const rgba_blue = 0xFFFF0000; float const luma_blue = 0.0722; - float const error_margin = 0.01; + float const error_margin = 0.1; float const luma_gray = 0.50; + static constexpr std::chrono::milliseconds EVENT_WAIT_TIME_MS = 5000ms; }; TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) { @@ -261,7 +262,7 @@ TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) { composer->removeRegionSamplingListener(listener); } -TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { +TEST_F(RegionSamplingTest, CollectsLuma) { fill_render(rgba_green); sp composer = ComposerServiceAIDL::getComposerService(); @@ -273,7 +274,30 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) { sampleArea.bottom = 200; composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; + EXPECT_NEAR(listener->luma(), luma_green, error_margin); + + composer->removeRegionSamplingListener(listener); +} + +TEST_F(RegionSamplingTest, CollectsLumaForSecureLayer) { + fill_render(rgba_green); + SurfaceComposerClient::Transaction() + .setFlags(mContentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure) + .apply(/*synchronous=*/true); + + sp composer = ComposerServiceAIDL::getComposerService(); + sp listener = new Listener(); + gui::ARect sampleArea; + sampleArea.left = 100; + sampleArea.top = 100; + sampleArea.right = 200; + sampleArea.bottom = 200; + composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); + + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_green, error_margin); composer->removeRegionSamplingListener(listener); @@ -291,13 +315,14 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsChangingLuma) { sampleArea.bottom = 200; composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_green, error_margin); listener->reset(); fill_render(rgba_blue); - EXPECT_TRUE(listener->wait_event(300ms)) + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) << "timed out waiting for 2nd luma event to be received"; EXPECT_NEAR(listener->luma(), luma_blue, error_margin); @@ -323,10 +348,10 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) { graySampleArea.bottom = 200; composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener); - EXPECT_TRUE(grayListener->wait_event(300ms)) + EXPECT_TRUE(grayListener->wait_event(EVENT_WAIT_TIME_MS)) << "timed out waiting for luma event to be received"; EXPECT_NEAR(grayListener->luma(), luma_gray, error_margin); - EXPECT_TRUE(greenListener->wait_event(300ms)) + EXPECT_TRUE(greenListener->wait_event(EVENT_WAIT_TIME_MS)) << "timed out waiting for luma event to be received"; EXPECT_NEAR(greenListener->luma(), luma_green, error_margin); @@ -334,7 +359,7 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromTwoRegions) { composer->removeRegionSamplingListener(grayListener); } -TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) { +TEST_F(RegionSamplingTest, TestIfInvalidInputParameters) { sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); @@ -369,7 +394,7 @@ TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) { composer->removeRegionSamplingListener(listener); } -TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { +TEST_F(RegionSamplingTest, TestCallbackAfterRemoveListener) { fill_render(rgba_green); sp composer = ComposerServiceAIDL::getComposerService(); sp listener = new Listener(); @@ -381,7 +406,8 @@ TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) { composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener); fill_render(rgba_green); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_green, error_margin); listener->reset(); @@ -404,11 +430,13 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) { // Test: listener in (100, 100). See layer before move, no layer after move. fill_render(rgba_blue); composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_blue, error_margin); listener->reset(); SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply(); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_gray, error_margin); composer->removeRegionSamplingListener(listener); @@ -420,11 +448,13 @@ TEST_F(RegionSamplingTest, DISABLED_CollectsLumaFromMovingLayer) { sampleAreaA.right = sampleArea.right; sampleAreaA.bottom = sampleArea.bottom; composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_gray, error_margin); listener->reset(); SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply(); - EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received"; + EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS)) + << "timed out waiting for luma event to be received"; EXPECT_NEAR(listener->luma(), luma_green, error_margin); composer->removeRegionSamplingListener(listener); } diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp index 55b395b458..c63c738d34 100644 --- a/services/surfaceflinger/DisplayRenderArea.cpp +++ b/services/surfaceflinger/DisplayRenderArea.cpp @@ -22,22 +22,20 @@ namespace android { std::unique_ptr DisplayRenderArea::create(wp displayWeak, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool hintForSeamlessTransition, - bool allowSecureLayers) { + ftl::Flags options) { if (auto display = displayWeak.promote()) { // Using new to access a private constructor. - return std::unique_ptr( - new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace, - hintForSeamlessTransition, allowSecureLayers)); + return std::unique_ptr(new DisplayRenderArea(std::move(display), + sourceCrop, reqSize, + reqDataSpace, options)); } return nullptr; } DisplayRenderArea::DisplayRenderArea(sp display, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool hintForSeamlessTransition, bool allowSecureLayers) - : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition, - allowSecureLayers), + ftl::Flags options) + : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, options), mDisplay(std::move(display)), mSourceCrop(sourceCrop) {} @@ -46,7 +44,7 @@ const ui::Transform& DisplayRenderArea::getTransform() const { } bool DisplayRenderArea::isSecure() const { - return mAllowSecureLayers && mDisplay->isSecure(); + return mOptions.test(Options::CAPTURE_SECURE_LAYERS) && mDisplay->isSecure(); } sp DisplayRenderArea::getDisplayDevice() const { diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h index 4555a9ed66..677d01975a 100644 --- a/services/surfaceflinger/DisplayRenderArea.h +++ b/services/surfaceflinger/DisplayRenderArea.h @@ -29,8 +29,7 @@ class DisplayRenderArea : public RenderArea { public: static std::unique_ptr create(wp, const Rect& sourceCrop, ui::Size reqSize, ui::Dataspace, - bool hintForSeamlessTransition, - bool allowSecureLayers = true); + ftl::Flags options); const ui::Transform& getTransform() const override; bool isSecure() const override; @@ -39,7 +38,7 @@ public: private: DisplayRenderArea(sp, const Rect& sourceCrop, ui::Size reqSize, - ui::Dataspace, bool hintForSeamlessTransition, bool allowSecureLayers = true); + ui::Dataspace, ftl::Flags options); const sp mDisplay; const Rect mSourceCrop; diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp index f323ce7284..bfe6d2a956 100644 --- a/services/surfaceflinger/LayerRenderArea.cpp +++ b/services/surfaceflinger/LayerRenderArea.cpp @@ -27,10 +27,9 @@ namespace android { LayerRenderArea::LayerRenderArea(sp layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool allowSecureLayers, const ui::Transform& layerTransform, - const Rect& layerBufferSize, bool hintForSeamlessTransition) - : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition, - allowSecureLayers), + const ui::Transform& layerTransform, const Rect& layerBufferSize, + ftl::Flags options) + : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, options), mLayer(std::move(layer)), mLayerSnapshot(std::move(layerSnapshot)), mLayerBufferSize(layerBufferSize), @@ -42,7 +41,7 @@ const ui::Transform& LayerRenderArea::getTransform() const { } bool LayerRenderArea::isSecure() const { - return mAllowSecureLayers; + return mOptions.test(Options::CAPTURE_SECURE_LAYERS); } sp LayerRenderArea::getDisplayDevice() const { diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h index a12bfca54a..f72c7c7715 100644 --- a/services/surfaceflinger/LayerRenderArea.h +++ b/services/surfaceflinger/LayerRenderArea.h @@ -33,9 +33,9 @@ class SurfaceFlinger; class LayerRenderArea : public RenderArea { public: LayerRenderArea(sp layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop, - ui::Size reqSize, ui::Dataspace reqDataSpace, bool allowSecureLayers, + ui::Size reqSize, ui::Dataspace reqDataSpace, const ui::Transform& layerTransform, const Rect& layerBufferSize, - bool hintForSeamlessTransition); + ftl::Flags options); const ui::Transform& getTransform() const override; bool isSecure() const override; diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 5add290e96..e579eb08a6 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -277,7 +277,6 @@ void RegionSamplingThread::captureSample() { } const Rect sampledBounds = sampleRegion.bounds(); - constexpr bool kHintForSeamlessTransition = false; std::unordered_set, SpHash> listeners; @@ -350,9 +349,8 @@ void RegionSamplingThread::captureSample() { SurfaceFlinger::RenderAreaBuilderVariant renderAreaBuilder(std::in_place_type, sampledBounds, - sampledBounds.getSize(), ui::Dataspace::V0_SRGB, - kHintForSeamlessTransition, true /* captureSecureLayers */, - displayWeak); + sampledBounds.getSize(), ui::Dataspace::V0_SRGB, displayWeak, + RenderArea::Options::CAPTURE_SECURE_LAYERS); FenceResult fenceResult; if (FlagManager::getInstance().single_hop_screenshot() && diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h index e8d20af4ad..034e467be9 100644 --- a/services/surfaceflinger/RenderArea.h +++ b/services/surfaceflinger/RenderArea.h @@ -21,16 +21,23 @@ class DisplayDevice; class RenderArea { public: enum class CaptureFill {CLEAR, OPAQUE}; - + enum class Options { + // If not set, the secure layer would be blacked out or skipped + // when rendered to an insecure render area + CAPTURE_SECURE_LAYERS = 1 << 0, + + // If set, the render result may be used for system animations + // that must preserve the exact colors of the display + HINT_FOR_SEAMLESS_TRANSITION = 1 << 1, + }; static float getCaptureFillValue(CaptureFill captureFill); RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace, - bool hintForSeamlessTransition, bool allowSecureLayers = false) - : mAllowSecureLayers(allowSecureLayers), + ftl::Flags options) + : mOptions(options), mReqSize(reqSize), mReqDataSpace(reqDataSpace), - mCaptureFill(captureFill), - mHintForSeamlessTransition(hintForSeamlessTransition) {} + mCaptureFill(captureFill) {} static std::function>>()> fromTraverseLayersLambda( std::function traverseLayers) { @@ -90,16 +97,17 @@ public: // Returns whether the render result may be used for system animations that // must preserve the exact colors of the display. - bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; } + bool getHintForSeamlessTransition() const { + return mOptions.test(Options::HINT_FOR_SEAMLESS_TRANSITION); + } protected: - const bool mAllowSecureLayers; + ftl::Flags mOptions; private: const ui::Size mReqSize; const ui::Dataspace mReqDataSpace; const CaptureFill mCaptureFill; - const bool mHintForSeamlessTransition; }; } // namespace android diff --git a/services/surfaceflinger/RenderAreaBuilder.h b/services/surfaceflinger/RenderAreaBuilder.h index a25c6e0b67..599fa7e102 100644 --- a/services/surfaceflinger/RenderAreaBuilder.h +++ b/services/surfaceflinger/RenderAreaBuilder.h @@ -36,50 +36,34 @@ struct RenderAreaBuilder { // Composition data space of the render area ui::Dataspace reqDataSpace; - // If true, the secure layer would be blacked out or skipped - // when rendered to an insecure render area - bool allowSecureLayers; - - // If true, the render result may be used for system animations - // that must preserve the exact colors of the display - bool hintForSeamlessTransition; - + ftl::Flags options; virtual std::unique_ptr build() const = 0; RenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool allowSecureLayers, bool hintForSeamlessTransition) - : crop(crop), - reqSize(reqSize), - reqDataSpace(reqDataSpace), - allowSecureLayers(allowSecureLayers), - hintForSeamlessTransition(hintForSeamlessTransition) {} + ftl::Flags options) + : crop(crop), reqSize(reqSize), reqDataSpace(reqDataSpace), options(options) {} virtual ~RenderAreaBuilder() = default; }; struct DisplayRenderAreaBuilder : RenderAreaBuilder { DisplayRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool allowSecureLayers, bool hintForSeamlessTransition, - wp displayWeak) - : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers, - hintForSeamlessTransition), - displayWeak(displayWeak) {} + wp displayWeak, + ftl::Flags options) + : RenderAreaBuilder(crop, reqSize, reqDataSpace, options), displayWeak(displayWeak) {} // Display that render area will be on wp displayWeak; std::unique_ptr build() const override { - return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace, - hintForSeamlessTransition, allowSecureLayers); + return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace, options); } }; struct LayerRenderAreaBuilder : RenderAreaBuilder { - LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, - bool allowSecureLayers, bool hintForSeamlessTransition, sp layer, - bool childrenOnly) - : RenderAreaBuilder(crop, reqSize, reqDataSpace, allowSecureLayers, - hintForSeamlessTransition), + LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, sp layer, + bool childrenOnly, ftl::Flags options) + : RenderAreaBuilder(crop, reqSize, reqDataSpace, options), layer(layer), childrenOnly(childrenOnly) {} @@ -110,8 +94,8 @@ struct LayerRenderAreaBuilder : RenderAreaBuilder { std::unique_ptr build() const override { return std::make_unique(layer, std::move(layerSnapshot), crop, reqSize, - reqDataSpace, allowSecureLayers, layerTransform, - layerBufferSize, hintForSeamlessTransition); + reqDataSpace, layerTransform, layerBufferSize, + options); } }; diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 5fa6d26dfe..f38d2b6e7b 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7959,10 +7959,13 @@ void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args, GetLayerSnapshotsFunction getLayerSnapshotsFn = getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds)); + ftl::Flags options; + if (args.captureSecureLayers) options |= RenderArea::Options::CAPTURE_SECURE_LAYERS; + if (args.hintForSeamlessTransition) + options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION; captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, args.sourceCrop, reqSize, args.dataspace, - args.hintForSeamlessTransition, - args.captureSecureLayers, displayWeak), + displayWeak, options), getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); } @@ -8013,10 +8016,12 @@ void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args constexpr bool kAllowProtected = false; constexpr bool kGrayscale = false; + ftl::Flags options; + if (args.hintForSeamlessTransition) + options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION; captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, - Rect(), size, args.dataspace, - args.hintForSeamlessTransition, - false /* captureSecureLayers */, displayWeak), + Rect(), size, args.dataspace, displayWeak, + options), getLayerSnapshotsFn, size, args.pixelFormat, kAllowProtected, kGrayscale, captureListener); } @@ -8115,10 +8120,13 @@ void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args, return; } + ftl::Flags options; + if (args.captureSecureLayers) options |= RenderArea::Options::CAPTURE_SECURE_LAYERS; + if (args.hintForSeamlessTransition) + options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION; captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type, crop, - reqSize, dataspace, args.captureSecureLayers, - args.hintForSeamlessTransition, parent, - args.childrenOnly), + reqSize, dataspace, parent, args.childrenOnly, + options), getLayerSnapshotsFn, reqSize, args.pixelFormat, args.allowProtected, args.grayscale, captureListener); } diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp index 08973de195..cdd77fec95 100644 --- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp +++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp @@ -70,6 +70,7 @@ using testing::SetArgPointee; using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector; using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector; +using namespace ftl::flag_operators; constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID; constexpr hal::HWLayerId HWC_LAYER = 5000; @@ -197,8 +198,11 @@ void CompositionTest::captureScreenComposition() { const Rect sourceCrop(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT); constexpr bool regionSampling = false; - auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(), - ui::Dataspace::V0_SRGB, true, true); + auto renderArea = + DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(), + ui::Dataspace::V0_SRGB, + RenderArea::Options::CAPTURE_SECURE_LAYERS | + RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION); auto traverseLayers = [this](const LayerVector::Visitor& visitor) { return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(), -- GitLab From 052c51302e797260763a44b537820b7d54b18226 Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Fri, 21 Jun 2024 15:45:26 -0700 Subject: [PATCH 441/465] Binder: enable/fix some warnings Bug: 341997808 Test: mma Change-Id: Ibd44eda768a714e85399a15f3c8e534d715f87ce --- libs/binder/IPCThreadState.cpp | 2 + libs/binder/PersistableBundle.cpp | 4 +- libs/binder/Utils.h | 13 +++ .../include_ndk/android/persistable_bundle.h | 5 + libs/binder/ndk/service_manager.cpp | 3 + libs/binder/ndk/tests/Android.bp | 5 + libs/binder/ndk/tests/iface.cpp | 7 +- .../ndk/tests/libbinder_ndk_unit_test.cpp | 108 ++++++++---------- libs/binder/tests/binderClearBufTest.cpp | 5 +- .../tests/binderDriverInterfaceTest.cpp | 12 +- libs/binder/tests/binderLibTest.cpp | 27 ++--- libs/binder/tests/binderStabilityTest.cpp | 18 ++- 12 files changed, 110 insertions(+), 99 deletions(-) diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp index 94f947fce1..984c93d370 100644 --- a/libs/binder/IPCThreadState.cpp +++ b/libs/binder/IPCThreadState.cpp @@ -285,7 +285,9 @@ static const void* printCommand(std::ostream& out, const void* _cmd) { return cmd; } +LIBBINDER_IGNORE("-Wzero-as-null-pointer-constant") static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER; +LIBBINDER_IGNORE_END() static std::atomic gHaveTLS(false); static pthread_key_t gTLS = 0; static std::atomic gShutdown = false; diff --git a/libs/binder/PersistableBundle.cpp b/libs/binder/PersistableBundle.cpp index 5b157cc7c3..abb6612a1c 100644 --- a/libs/binder/PersistableBundle.cpp +++ b/libs/binder/PersistableBundle.cpp @@ -113,7 +113,7 @@ status_t PersistableBundle::writeToParcel(Parcel* parcel) const { // Backpatch length. This length value includes the length header. parcel->setDataPosition(length_pos); size_t length = end_pos - start_pos; - if (length > std::numeric_limits::max()) { + if (length > static_cast(std::numeric_limits::max())) { ALOGE("Parcel length (%zu) too large to store in 32-bit signed int", length); return BAD_VALUE; } @@ -319,7 +319,7 @@ status_t PersistableBundle::writeToParcelInner(Parcel* parcel) const { * pairs themselves. */ size_t num_entries = size(); - if (num_entries > std::numeric_limits::max()) { + if (num_entries > static_cast(std::numeric_limits::max())) { ALOGE("The size of this PersistableBundle (%zu) too large to store in 32-bit signed int", num_entries); return BAD_VALUE; diff --git a/libs/binder/Utils.h b/libs/binder/Utils.h index 5e1012af01..881cdf3f4c 100644 --- a/libs/binder/Utils.h +++ b/libs/binder/Utils.h @@ -58,6 +58,19 @@ } \ } while (0) +#define LIBBINDER_PRAGMA(arg) _Pragma(#arg) +#if defined(__clang__) +#define LIBBINDER_PRAGMA_FOR_COMPILER(arg) LIBBINDER_PRAGMA(clang arg) +#elif defined(__GNUC__) +#define LIBBINDER_PRAGMA_FOR_COMPILER(arg) LIBBINDER_PRAGMA(GCC arg) +#else +#define LIBBINDER_PRAGMA_FOR_COMPILER(arg) +#endif +#define LIBBINDER_IGNORE(warning_flag) \ + LIBBINDER_PRAGMA_FOR_COMPILER(diagnostic push) \ + LIBBINDER_PRAGMA_FOR_COMPILER(diagnostic ignored warning_flag) +#define LIBBINDER_IGNORE_END() LIBBINDER_PRAGMA_FOR_COMPILER(diagnostic pop) + namespace android { /** diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h index 42ae15ae61..5e0d4da97b 100644 --- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h +++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h @@ -29,6 +29,11 @@ #include #include +#ifndef __clang__ +#define _Nullable +#define _Nonnull +#endif + __BEGIN_DECLS /* diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp index 4436dbeed7..d6ac4acc29 100644 --- a/libs/binder/ndk/service_manager.cpp +++ b/libs/binder/ndk/service_manager.cpp @@ -18,6 +18,7 @@ #include #include +#include "../Utils.h" #include "ibinder_internal.h" #include "status_internal.h" @@ -89,7 +90,9 @@ AIBinder* AServiceManager_getService(const char* instance) { } sp sm = defaultServiceManager(); + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp binder = sm->getService(String16(instance)); + LIBBINDER_IGNORE_END() sp ret = ABpBinder::lookupOrCreateFromBinder(binder); AIBinder_incStrong(ret.get()); diff --git a/libs/binder/ndk/tests/Android.bp b/libs/binder/ndk/tests/Android.bp index 8fb755cdac..c61a16413b 100644 --- a/libs/binder/ndk/tests/Android.bp +++ b/libs/binder/ndk/tests/Android.bp @@ -34,6 +34,11 @@ cc_defaults { cflags: [ "-O0", "-g", + "-Wall", + "-Wextra", + "-Wextra-semi", + "-Werror", + "-Winconsistent-missing-override", ], } diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp index ca927272f8..08b857fab2 100644 --- a/libs/binder/ndk/tests/iface.cpp +++ b/libs/binder/ndk/tests/iface.cpp @@ -20,6 +20,8 @@ #include +#include "../../Utils.h" + using ::android::sp; using ::android::wp; @@ -157,10 +159,9 @@ binder_status_t IFoo::addService(const char* instance) { } sp IFoo::getService(const char* instance, AIBinder** outBinder) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") AIBinder* binder = AServiceManager_getService(instance); // maybe nullptr -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() if (binder == nullptr) { return nullptr; } diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp index 6d638d0b3a..f518a22902 100644 --- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp +++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp @@ -42,6 +42,7 @@ #include #include +#include "../Utils.h" #include "android/binder_ibinder.h" using namespace android; @@ -69,21 +70,21 @@ class MyTestFoo : public IFoo { }; class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest { - ndk::ScopedAStatus repeatInt(int32_t in, int32_t* out) { + ndk::ScopedAStatus repeatInt(int32_t in, int32_t* out) override { *out = in; return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus takeInterface(const std::shared_ptr& empty) { + ndk::ScopedAStatus takeInterface(const std::shared_ptr& empty) override { (void)empty; return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus forceFlushCommands() { + ndk::ScopedAStatus forceFlushCommands() override { // warning: this is assuming that libbinder_ndk is using the same copy // of libbinder that we are. android::IPCThreadState::self()->flushCommands(); return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus getsRequestedSid(bool* out) { + ndk::ScopedAStatus getsRequestedSid(bool* out) override { const char* sid = AIBinder_getCallingSid(); std::cout << "Got security context: " << (sid ?: "null") << std::endl; *out = sid != nullptr; @@ -97,11 +98,11 @@ class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest { fsync(out); return STATUS_OK; } - ndk::ScopedAStatus forcePersist(bool persist) { + ndk::ScopedAStatus forcePersist(bool persist) override { AServiceManager_forceLazyServicesPersist(persist); return ndk::ScopedAStatus::ok(); } - ndk::ScopedAStatus setCustomActiveServicesCallback() { + ndk::ScopedAStatus setCustomActiveServicesCallback() override { AServiceManager_setActiveServicesCallback(activeServicesCallback, this); return ndk::ScopedAStatus::ok(); } @@ -342,10 +343,9 @@ TEST(NdkBinder, UnimplementedShell) { // libbinder across processes to the NDK service which doesn't implement // shell static const sp sm(android::defaultServiceManager()); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp testService = sm->getService(String16(IFoo::kSomeInstanceName)); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() Vector argsVec; EXPECT_EQ(OK, IBinder::shellCommand(testService, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, @@ -388,10 +388,9 @@ TEST(NdkBinder, GetTestServiceStressTest) { // checkService on it, since the other process serving it might not be started yet. { // getService, not waitForService, to take advantage of timeout -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") auto binder = ndk::SpAIBinder(AServiceManager_getService(IFoo::kSomeInstanceName)); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_NE(nullptr, binder.get()); } @@ -425,7 +424,7 @@ TEST(NdkBinder, GetDeclaredInstances) { // At the time of writing this test, there is no good interface guaranteed // to be on all devices. Cuttlefish has light, so this will generally test // things. - EXPECT_EQ(count, hasLight ? 1 : 0); + EXPECT_EQ(count, hasLight ? 1u : 0u); } TEST(NdkBinder, GetLazyService) { @@ -515,7 +514,7 @@ void LambdaOnDeath(void* cookie) { // may reference other cookie members (*funcs->onDeath)(); -}; +} void LambdaOnUnlink(void* cookie) { auto funcs = static_cast(cookie); (*funcs->onUnlink)(); @@ -523,7 +522,7 @@ void LambdaOnUnlink(void* cookie) { // may reference other cookie members delete funcs; -}; +} TEST(NdkBinder, DeathRecipient) { using namespace std::chrono_literals; @@ -701,7 +700,7 @@ TEST(NdkBinder, DeathRecipientDropBinderOnDied) { void LambdaOnUnlinkMultiple(void* cookie) { auto funcs = static_cast(cookie); (*funcs->onUnlink)(); -}; +} TEST(NdkBinder, DeathRecipientMultipleLinks) { using namespace std::chrono_literals; @@ -733,7 +732,7 @@ TEST(NdkBinder, DeathRecipientMultipleLinks) { ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath)); AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlinkMultiple); - for (int32_t i = 0; i < kNumberOfLinksToDeath; i++) { + for (uint32_t i = 0; i < kNumberOfLinksToDeath; i++) { EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder.get(), recipient.get(), static_cast(cookie))); } @@ -745,14 +744,13 @@ TEST(NdkBinder, DeathRecipientMultipleLinks) { EXPECT_TRUE(unlinkCv.wait_for(lockUnlink, 5s, [&] { return unlinkReceived; })) << "countdown: " << countdown; EXPECT_TRUE(unlinkReceived); - EXPECT_EQ(countdown, 0); + EXPECT_EQ(countdown, 0u); } TEST(NdkBinder, RetrieveNonNdkService) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") AIBinder* binder = AServiceManager_getService(kExistingNonNdkService); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_NE(nullptr, binder); EXPECT_TRUE(AIBinder_isRemote(binder)); EXPECT_TRUE(AIBinder_isAlive(binder)); @@ -766,10 +764,9 @@ void OnBinderDeath(void* cookie) { } TEST(NdkBinder, LinkToDeath) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") AIBinder* binder = AServiceManager_getService(kExistingNonNdkService); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_NE(nullptr, binder); AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(OnBinderDeath); @@ -799,10 +796,9 @@ TEST(NdkBinder, SetInheritRt) { } TEST(NdkBinder, SetInheritRtNonLocal) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") AIBinder* binder = AServiceManager_getService(kExistingNonNdkService); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_NE(binder, nullptr); ASSERT_TRUE(AIBinder_isRemote(binder)); @@ -838,14 +834,13 @@ TEST(NdkBinder, GetServiceInProcess) { } TEST(NdkBinder, EqualityOfRemoteBinderPointer) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") AIBinder* binderA = AServiceManager_getService(kExistingNonNdkService); ASSERT_NE(nullptr, binderA); AIBinder* binderB = AServiceManager_getService(kExistingNonNdkService); ASSERT_NE(nullptr, binderB); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() EXPECT_EQ(binderA, binderB); @@ -859,10 +854,9 @@ TEST(NdkBinder, ToFromJavaNullptr) { } TEST(NdkBinder, ABpBinderRefCount) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") AIBinder* binder = AServiceManager_getService(kExistingNonNdkService); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() AIBinder_Weak* wBinder = AIBinder_Weak_new(binder); ASSERT_NE(nullptr, binder); @@ -885,10 +879,9 @@ TEST(NdkBinder, AddServiceMultipleTimes) { } TEST(NdkBinder, RequestedSidWorks) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") ndk::SpAIBinder binder(AServiceManager_getService(kBinderNdkUnitTestService)); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() std::shared_ptr service = aidl::IBinderNdkUnitTest::fromBinder(binder); @@ -911,10 +904,9 @@ TEST(NdkBinder, SentAidlBinderCanBeDestroyed) { std::shared_ptr empty = ndk::SharedRefBase::make(); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") ndk::SpAIBinder binder(AServiceManager_getService(kBinderNdkUnitTestService)); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() std::shared_ptr service = aidl::IBinderNdkUnitTest::fromBinder(binder); @@ -935,14 +927,11 @@ TEST(NdkBinder, SentAidlBinderCanBeDestroyed) { } TEST(NdkBinder, ConvertToPlatformBinder) { - for (const ndk::SpAIBinder& binder : - {// remote -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)), -#pragma clang diagnostic pop - // local - ndk::SharedRefBase::make()->asBinder()}) { + LIBBINDER_IGNORE("-Wdeprecated-declarations") + ndk::SpAIBinder remoteBinder(AServiceManager_getService(kBinderNdkUnitTestService)); + LIBBINDER_IGNORE_END() + auto localBinder = ndk::SharedRefBase::make()->asBinder(); + for (const ndk::SpAIBinder& binder : {remoteBinder, localBinder}) { // convert to platform binder EXPECT_NE(binder, nullptr); sp platformBinder = AIBinder_toPlatformBinder(binder.get()); @@ -971,14 +960,11 @@ TEST(NdkBinder, ConvertToPlatformParcel) { } TEST(NdkBinder, GetAndVerifyScopedAIBinder_Weak) { - for (const ndk::SpAIBinder& binder : - {// remote -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)), -#pragma clang diagnostic pop - // local - ndk::SharedRefBase::make()->asBinder()}) { + LIBBINDER_IGNORE("-Wdeprecated-declarations") + ndk::SpAIBinder remoteBinder(AServiceManager_getService(kBinderNdkUnitTestService)); + LIBBINDER_IGNORE_END() + auto localBinder = ndk::SharedRefBase::make()->asBinder(); + for (const ndk::SpAIBinder& binder : {remoteBinder, localBinder}) { // get a const ScopedAIBinder_Weak and verify promote EXPECT_NE(binder.get(), nullptr); const ndk::ScopedAIBinder_Weak wkAIBinder = @@ -1047,7 +1033,7 @@ std::string shellCmdToString(sp unitTestService, const std::vector resultReceiver = new MyResultReceiver(); Vector argsVec; - for (int i = 0; i < args.size(); i++) { + for (size_t i = 0; i < args.size(); i++) { argsVec.add(String16(args[i])); } status_t error = IBinder::shellCommand(unitTestService, inFd[0], outFd[0], errFd[0], argsVec, @@ -1071,10 +1057,9 @@ std::string shellCmdToString(sp unitTestService, const std::vector sm(android::defaultServiceManager()); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp testService = sm->getService(String16(kBinderNdkUnitTestService)); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() EXPECT_EQ("", shellCmdToString(testService, {})); EXPECT_EQ("", shellCmdToString(testService, {"", ""})); @@ -1084,10 +1069,9 @@ TEST(NdkBinder, UseHandleShellCommand) { TEST(NdkBinder, FlaggedServiceAccessible) { static const sp sm(android::defaultServiceManager()); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp testService = sm->getService(String16(kBinderNdkUnitTestServiceFlagged)); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_NE(nullptr, testService); } diff --git a/libs/binder/tests/binderClearBufTest.cpp b/libs/binder/tests/binderClearBufTest.cpp index 3230a3f904..63254cddd9 100644 --- a/libs/binder/tests/binderClearBufTest.cpp +++ b/libs/binder/tests/binderClearBufTest.cpp @@ -75,10 +75,9 @@ class FooBar : public BBinder { }; TEST(BinderClearBuf, ClearKernelBuffer) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp binder = defaultServiceManager()->getService(kServerName); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_NE(nullptr, binder); std::string replyBuffer; diff --git a/libs/binder/tests/binderDriverInterfaceTest.cpp b/libs/binder/tests/binderDriverInterfaceTest.cpp index de9d42bac0..7be4f21cee 100644 --- a/libs/binder/tests/binderDriverInterfaceTest.cpp +++ b/libs/binder/tests/binderDriverInterfaceTest.cpp @@ -94,8 +94,9 @@ class BinderDriverInterfaceTest : public ::testing::Test { ret = ioctl(m_binderFd, cmd, arg); EXPECT_EQ(expect_ret, ret); if (ret < 0) { - if (errno != accept_errno) + if (errno != accept_errno) { EXPECT_EQ(expect_errno, errno); + } } } void binderTestIoctlErr2(int cmd, void *arg, int expect_errno, int accept_errno) { @@ -275,12 +276,15 @@ TEST_F(BinderDriverInterfaceTest, Transaction) { binderTestIoctl(BINDER_WRITE_READ, &bwr); } EXPECT_EQ(offsetof(typeof(br), pad), bwr.read_consumed); - if (bwr.read_consumed > offsetof(typeof(br), cmd0)) + if (bwr.read_consumed > offsetof(typeof(br), cmd0)) { EXPECT_EQ(BR_NOOP, br.cmd0); - if (bwr.read_consumed > offsetof(typeof(br), cmd1)) + } + if (bwr.read_consumed > offsetof(typeof(br), cmd1)) { EXPECT_EQ(BR_TRANSACTION_COMPLETE, br.cmd1); - if (bwr.read_consumed > offsetof(typeof(br), cmd2)) + } + if (bwr.read_consumed > offsetof(typeof(br), cmd2)) { EXPECT_EQ(BR_REPLY, br.cmd2); + } if (bwr.read_consumed >= offsetof(typeof(br), pad)) { EXPECT_EQ(0u, br.arg2.target.ptr); EXPECT_EQ(0u, br.arg2.cookie); diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp index 5582a96ac1..9b1ba01146 100644 --- a/libs/binder/tests/binderLibTest.cpp +++ b/libs/binder/tests/binderLibTest.cpp @@ -47,10 +47,9 @@ #include #include +#include "../Utils.h" #include "../binder_module.h" -#define ARRAY_SIZE(array) (sizeof array / sizeof array[0]) - using namespace android; using namespace android::binder::impl; using namespace std::string_literals; @@ -216,10 +215,9 @@ class BinderLibTestEnv : public ::testing::Environment { sp sm = defaultServiceManager(); //printf("%s: pid %d, get service\n", __func__, m_pid); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") m_server = sm->getService(binderLibTestServiceName); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() ASSERT_TRUE(m_server != nullptr); //printf("%s: pid %d, get service done\n", __func__, m_pid); } @@ -566,7 +564,7 @@ TEST_F(BinderLibTest, Freeze) { TEST_F(BinderLibTest, SetError) { int32_t testValue[] = { 0, -123, 123 }; - for (size_t i = 0; i < ARRAY_SIZE(testValue); i++) { + for (size_t i = 0; i < countof(testValue); i++) { Parcel data, reply; data.writeInt32(testValue[i]); EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_SET_ERROR_TRANSACTION, data, &reply), @@ -597,8 +595,8 @@ TEST_F(BinderLibTest, IndirectGetId2) Parcel data, reply; int32_t serverId[3]; - data.writeInt32(ARRAY_SIZE(serverId)); - for (size_t i = 0; i < ARRAY_SIZE(serverId); i++) { + data.writeInt32(countof(serverId)); + for (size_t i = 0; i < countof(serverId); i++) { sp server; BinderLibTestBundle datai; @@ -616,7 +614,7 @@ TEST_F(BinderLibTest, IndirectGetId2) EXPECT_EQ(0, id); ASSERT_THAT(reply.readInt32(&count), StatusEq(NO_ERROR)); - EXPECT_EQ(ARRAY_SIZE(serverId), (size_t)count); + EXPECT_EQ(countof(serverId), (size_t)count); for (size_t i = 0; i < (size_t)count; i++) { BinderLibTestBundle replyi(&reply); @@ -636,8 +634,8 @@ TEST_F(BinderLibTest, IndirectGetId3) Parcel data, reply; int32_t serverId[3]; - data.writeInt32(ARRAY_SIZE(serverId)); - for (size_t i = 0; i < ARRAY_SIZE(serverId); i++) { + data.writeInt32(countof(serverId)); + for (size_t i = 0; i < countof(serverId); i++) { sp server; BinderLibTestBundle datai; BinderLibTestBundle datai2; @@ -662,7 +660,7 @@ TEST_F(BinderLibTest, IndirectGetId3) EXPECT_EQ(0, id); ASSERT_THAT(reply.readInt32(&count), StatusEq(NO_ERROR)); - EXPECT_EQ(ARRAY_SIZE(serverId), (size_t)count); + EXPECT_EQ(countof(serverId), (size_t)count); for (size_t i = 0; i < (size_t)count; i++) { int32_t counti; @@ -2112,10 +2110,9 @@ int run_server(int index, int readypipefd, bool usePoll) if (index == 0) { ret = sm->addService(binderLibTestServiceName, testService); } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp server = sm->getService(binderLibTestServiceName); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() Parcel data, reply; data.writeInt32(index); data.writeStrongBinder(testService); diff --git a/libs/binder/tests/binderStabilityTest.cpp b/libs/binder/tests/binderStabilityTest.cpp index 3d993588a4..7a8f48e0eb 100644 --- a/libs/binder/tests/binderStabilityTest.cpp +++ b/libs/binder/tests/binderStabilityTest.cpp @@ -27,8 +27,9 @@ #include -#include "aidl/BnBinderStabilityTest.h" +#include "../Utils.h" #include "BnBinderStabilityTest.h" +#include "aidl/BnBinderStabilityTest.h" using namespace android; using namespace ndk; @@ -155,10 +156,9 @@ TEST(BinderStability, NdkForceDowngradeToLocalStability) { } TEST(BinderStability, ForceDowngradeToVendorStability) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp serverBinder = android::defaultServiceManager()->getService(kSystemStabilityServer); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() auto server = interface_cast(serverBinder); ASSERT_NE(nullptr, server.get()); @@ -209,10 +209,9 @@ TEST(BinderStability, ConnectionInfoRequiresManifestEntries) { EXPECT_EQ(connectionInfo, std::nullopt); } TEST(BinderStability, CantCallVendorBinderInSystemContext) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") sp serverBinder = android::defaultServiceManager()->getService(kSystemStabilityServer); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() auto server = interface_cast(serverBinder); ASSERT_NE(nullptr, server.get()); @@ -316,11 +315,10 @@ static AIBinder_Class* kNdkBadStableBinder = extern "C" void AIBinder_markVendorStability(AIBinder* binder); // <- BAD DO NOT COPY TEST(BinderStability, NdkCantCallVendorBinderInSystemContext) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" + LIBBINDER_IGNORE("-Wdeprecated-declarations") SpAIBinder binder = SpAIBinder(AServiceManager_getService( String8(kSystemStabilityServer).c_str())); -#pragma clang diagnostic pop + LIBBINDER_IGNORE_END() std::shared_ptr remoteServer = aidl::IBinderStabilityTest::fromBinder(binder); -- GitLab From 4619b2a30aa56b3d764a2d8d954163fbcfda20f6 Mon Sep 17 00:00:00 2001 From: Justin Yun Date: Fri, 28 Jun 2024 13:37:32 +0900 Subject: [PATCH 442/465] Define android.software.credentials.xml as prebuilt_etc Instead of using PRODUCT_COPY_FILES to copy a source file to install, define a prebuilt_etc module and add it to the PRODUCT_PACKAGES. android.software.credentials.xml is to be installed in system for GSI. Bug: 345373198 Test: See if android.software.credentials.xml is installed in system/etc/permissions (cherry picked from https://android-review.googlesource.com/q/commit:e1f9d9778f80c498b6c6747157006302224e652f) Merged-In: Ib9f030e1e0ff6db55a6e07444943407f9ff1c1df Change-Id: Ib9f030e1e0ff6db55a6e07444943407f9ff1c1df --- data/etc/Android.bp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 0300f8c195..4a66c88dcc 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -376,6 +376,14 @@ prebuilt_etc { defaults: ["frameworks_native_data_etc_defaults"], } +// installed in system for GSI +prebuilt_etc { + name: "android.software.credentials.prebuilt.xml", + relative_install_path: "permissions", + src: "android.software.credentials.xml", + filename_from_src: true, +} + prebuilt_etc { name: "android.software.device_id_attestation.prebuilt.xml", src: "android.software.device_id_attestation.xml", -- GitLab From 454f7f2768a4f7263fe9723e652786da1b0e90fe Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 29 Jun 2024 01:17:02 +0000 Subject: [PATCH 443/465] Enable single hop screenshots for only threaded re Fixes a deadlock where screenshot requests are blocked on the main thread which inturn is blocked by the screenshot request finishing. Flag: EXEMPT bug fix Bug: 349776684 Test: presubmit (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5aadd249f326b21e5ef6cadfebc15b6a0a016816) Merged-In: Ibf038ad6db3e87c84508d3e101ca1eb144836d7c Change-Id: Ibf038ad6db3e87c84508d3e101ca1eb144836d7c --- services/surfaceflinger/RegionSamplingThread.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index e579eb08a6..c77bcfa6ed 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -354,7 +354,7 @@ void RegionSamplingThread::captureSample() { FenceResult fenceResult; if (FlagManager::getInstance().single_hop_screenshot() && - FlagManager::getInstance().ce_fence_promise()) { + FlagManager::getInstance().ce_fence_promise() && mFlinger.mRenderEngine->isThreaded()) { std::vector> layerFEs; auto displayState = mFlinger.getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index f38d2b6e7b..b8dd34d4f8 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8194,7 +8194,7 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuil } if (FlagManager::getInstance().single_hop_screenshot() && - FlagManager::getInstance().ce_fence_promise()) { + FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) { std::vector> layerFEs; auto displayState = getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, @@ -8568,10 +8568,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( // to CompositionEngine::present. ftl::SharedFuture presentFuture; if (FlagManager::getInstance().single_hop_screenshot() && - FlagManager::getInstance().ce_fence_promise()) { - presentFuture = mRenderEngine->isThreaded() - ? ftl::yield(present()).share() - : mScheduler->schedule(std::move(present)).share(); + FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) { + presentFuture = ftl::yield(present()).share(); } else { presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share() : ftl::yield(present()).share(); -- GitLab From f5df97188715aacf354f682f3c53d46820ffe36e Mon Sep 17 00:00:00 2001 From: Tom Murphy Date: Tue, 25 Jun 2024 21:58:26 +0000 Subject: [PATCH 444/465] Fix race condition in extension string creation Change a17429660467548a1bc5f8d435a327500fbe972b introduced a race condition where the notfiy_all is called before the mExtensionString is set. Set mExtensionString before refCond.notify_all(); is called Bug: 345419965 Bug: 346881664 Test: Ran EGL unit tests (cherry picked from https://android-review.googlesource.com/q/commit:5e94634dc57024dcae2d1484758c2a9a7a55d55e) Merged-In: Ifcb691f9b9769be494e204c97512c3f7fffec60a Change-Id: Ifcb691f9b9769be494e204c97512c3f7fffec60a --- opengl/libs/EGL/egl_display.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index 5b5afd33c6..b1a287fcec 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -383,6 +383,14 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { // before using cnx->major & cnx->minor if (major != nullptr) *major = cnx->major; if (minor != nullptr) *minor = cnx->minor; + auto mergeExtensionStrings = [](const std::vector& strings) { + std::ostringstream combinedStringStream; + std::copy(strings.begin(), strings.end(), + std::ostream_iterator(combinedStringStream, " ")); + // gBuiltinExtensionString already has a trailing space so is added here + return gBuiltinExtensionString + combinedStringStream.str(); + }; + mExtensionString = mergeExtensionStrings(extensionStrings); } { // scope for refLock @@ -391,14 +399,6 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { refCond.notify_all(); } - auto mergeExtensionStrings = [](const std::vector& strings) { - std::ostringstream combinedStringStream; - std::copy(strings.begin(), strings.end(), - std::ostream_iterator(combinedStringStream, " ")); - // gBuiltinExtensionString already has a trailing space so is added here - return gBuiltinExtensionString + combinedStringStream.str(); - }; - mExtensionString = mergeExtensionStrings(extensionStrings); return EGL_TRUE; } -- GitLab From 27fc9f1fb7d447f17335da2b5bac186d0b1fcffc Mon Sep 17 00:00:00 2001 From: Siarhei Vishniakou Date: Sat, 13 Jul 2024 23:59:47 -0700 Subject: [PATCH 445/465] Handle non-split pointers for multi-device case During multi-device event stream, tempTouchState will already contain some pointers from another case. When the second device becomes active and tries to add pointers to a non-splitting window, we should not be marking that pointer as going to the second window unless it's already receiving pointers for that device. Fixes: 341869464 Flag: EXEMPT bugfix Test: TEST=inputflinger_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:962044645a27baaaf9d7736f76c6c252aa82c419) Merged-In: Ia295c1d9d941383792e90a3d6fd981473af8f015 Change-Id: Ia295c1d9d941383792e90a3d6fd981473af8f015 --- .../dispatcher/InputDispatcher.cpp | 3 + services/inputflinger/tests/FakeWindows.h | 7 ++ .../tests/InputDispatcher_test.cpp | 66 +++++++++++++++++++ .../inputflinger/tests/TestEventMatchers.h | 31 +++++++-- 4 files changed, 103 insertions(+), 4 deletions(-) diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index a76271da4e..f9fbfef3ec 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -2705,6 +2705,9 @@ std::vector InputDispatcher::findTouchedWindowTargetsLocked( if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) { continue; } + if (!touchedWindow.hasTouchingPointers(entry.deviceId)) { + continue; + } touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers); } } diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index ee65d3a375..3a3238a6ad 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -304,6 +304,13 @@ public: WithFlags(expectedFlags))); } + inline void consumeMotionPointerDown(int32_t pointerIdx, + const ::testing::Matcher& matcher) { + const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN | + (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher)); + } + inline void consumeMotionPointerUp(int32_t pointerIdx, const ::testing::Matcher& matcher) { const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 5eab6be074..2056372f2d 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -5357,6 +5357,72 @@ TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) { rightWindow->assertNoEvents(); } +/** + * Two windows: left and right. The left window has PREVENT_SPLITTING input config. Device A sends a + * down event to the right window. Device B sends a down event to the left window, and then a + * POINTER_DOWN event to the right window. However, since the left window prevents splitting, the + * POINTER_DOWN event should only go to the left window, and not to the right window. + * This test attempts to reproduce a crash. + */ +TEST_F(InputDispatcherTest, MultiDeviceTwoWindowsPreventSplitting) { + std::shared_ptr application = std::make_shared(); + sp leftWindow = + sp::make(application, mDispatcher, "Left window (prevent splitting)", + ui::LogicalDisplayId::DEFAULT); + leftWindow->setFrame(Rect(0, 0, 100, 100)); + leftWindow->setPreventSplitting(true); + + sp rightWindow = + sp::make(application, mDispatcher, "Right window", + ui::LogicalDisplayId::DEFAULT); + rightWindow->setFrame(Rect(100, 0, 200, 100)); + + mDispatcher->onWindowInfosChanged( + {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0}); + + const DeviceId deviceA = 9; + const DeviceId deviceB = 3; + // Touch the right window with device A + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .deviceId(deviceA) + .build()); + rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA))); + // Touch the left window with device B + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .deviceId(deviceB) + .build()); + leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB))); + // Send a second pointer from device B to the right window. It shouldn't go to the right window + // because the left window prevents splitting. + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(deviceB) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + leftWindow->consumeMotionPointerDown(1, WithDeviceId(deviceB)); + + // Finish the gesture for both devices + mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN) + .deviceId(deviceB) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120)) + .build()); + leftWindow->consumeMotionPointerUp(1, WithDeviceId(deviceB)); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .deviceId(deviceB) + .build()); + leftWindow->consumeMotionEvent( + AllOf(WithMotionAction(ACTION_UP), WithDeviceId(deviceB), WithPointerId(0, 0))); + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50)) + .deviceId(deviceA) + .build()); + rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(deviceA))); +} + TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) { std::shared_ptr application = std::make_shared(); sp window = sp::make(application, mDispatcher, "Window", diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h index 65fb9c6659..a6d9d5b0b2 100644 --- a/services/inputflinger/tests/TestEventMatchers.h +++ b/services/inputflinger/tests/TestEventMatchers.h @@ -609,10 +609,33 @@ MATCHER_P(WithRepeatCount, repeatCount, "KeyEvent with specified repeat count") return arg.getRepeatCount() == repeatCount; } -MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") { - const auto argPointerId = arg.pointerProperties[index].id; - *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId; - return argPointerId == id; +class WithPointerIdMatcher { +public: + using is_gtest_matcher = void; + explicit WithPointerIdMatcher(size_t index, int32_t pointerId) + : mIndex(index), mPointerId(pointerId) {} + + bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const { + return args.pointerProperties[mIndex].id == mPointerId; + } + + bool MatchAndExplain(const MotionEvent& event, std::ostream*) const { + return event.getPointerId(mIndex) == mPointerId; + } + + void DescribeTo(std::ostream* os) const { + *os << "with pointer[" << mIndex << "] id = " << mPointerId; + } + + void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointerId"; } + +private: + const size_t mIndex; + const int32_t mPointerId; +}; + +inline WithPointerIdMatcher WithPointerId(size_t index, int32_t pointerId) { + return WithPointerIdMatcher(index, pointerId); } MATCHER_P2(WithCursorPosition, x, y, "InputEvent with specified cursor position") { -- GitLab From e2946321a3fac1f7ae3782397a997accf4e21baf Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 10 Jul 2024 17:45:29 -0700 Subject: [PATCH 446/465] SF: add a new behaviour for idle timer on VRR When idle timer times out on VRR, change the refresh rate indicator to show "- -". The render rate doesn't cange as a result of idleness. Bug: 333443503 Test: manual Flag: EXEMPT bugfix (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:eba039c2d28646c5064a9a952faaba0cff6350cb) Merged-In: Ie4f51a2a9da1a5e229b3504881117b12f1fd1b6a Change-Id: Ie4f51a2a9da1a5e229b3504881117b12f1fd1b6a --- services/surfaceflinger/DisplayDevice.cpp | 6 +++ services/surfaceflinger/DisplayDevice.h | 1 + .../surfaceflinger/RefreshRateOverlay.cpp | 50 ++++++++++++++----- services/surfaceflinger/RefreshRateOverlay.h | 12 +++-- .../Scheduler/ISchedulerCallback.h | 1 + .../Scheduler/RefreshRateSelector.cpp | 3 +- .../Scheduler/RefreshRateSelector.h | 6 ++- .../surfaceflinger/Scheduler/Scheduler.cpp | 5 +- services/surfaceflinger/SurfaceFlinger.cpp | 16 ++++++ services/surfaceflinger/SurfaceFlinger.h | 1 + .../unittests/mock/MockSchedulerCallback.h | 2 + 11 files changed, 82 insertions(+), 21 deletions(-) diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp index 27ea4a9865..05f4da26be 100644 --- a/services/surfaceflinger/DisplayDevice.cpp +++ b/services/surfaceflinger/DisplayDevice.cpp @@ -467,6 +467,12 @@ bool DisplayDevice::onKernelTimerChanged(std::optional desiredMod return false; } +void DisplayDevice::onVrrIdle(bool idle) { + if (mRefreshRateOverlay) { + mRefreshRateOverlay->onVrrIdle(idle); + } +} + void DisplayDevice::animateOverlay() { if (mRefreshRateOverlay) { mRefreshRateOverlay->animate(); diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h index 3cc8cf5d63..1b8a3a8f54 100644 --- a/services/surfaceflinger/DisplayDevice.h +++ b/services/surfaceflinger/DisplayDevice.h @@ -196,6 +196,7 @@ public: bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; } void animateOverlay(); bool onKernelTimerChanged(std::optional, bool timerExpired); + void onVrrIdle(bool idle); // Enables an overlay to be display with the hdr/sdr ratio void enableHdrSdrRatioOverlay(bool enable) REQUIRES(kMainThreadContext); diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp index 9527a997df..35f12a0484 100644 --- a/services/surfaceflinger/RefreshRateOverlay.cpp +++ b/services/surfaceflinger/RefreshRateOverlay.cpp @@ -28,10 +28,11 @@ namespace android { -auto RefreshRateOverlay::draw(int refreshRate, int renderFps, SkColor color, +auto RefreshRateOverlay::draw(int refreshRate, int renderFps, bool idle, SkColor color, ui::Transform::RotationFlags rotation, ftl::Flags features) -> Buffers { const size_t loopCount = features.test(Features::Spinner) ? 6 : 1; + const bool isSetByHwc = features.test(Features::SetByHwc); Buffers buffers; buffers.reserve(loopCount); @@ -71,7 +72,11 @@ auto RefreshRateOverlay::draw(int refreshRate, int renderFps, SkColor color, canvas->setMatrix(canvasTransform); int left = 0; - drawNumber(refreshRate, left, color, *canvas); + if (idle && !isSetByHwc) { + drawDash(left, *canvas); + } else { + drawNumber(refreshRate, left, color, *canvas); + } left += 3 * (kDigitWidth + kDigitSpace); if (features.test(Features::Spinner)) { switch (i) { @@ -104,7 +109,11 @@ auto RefreshRateOverlay::draw(int refreshRate, int renderFps, SkColor color, left += kDigitWidth + kDigitSpace; if (features.test(Features::RenderRate)) { - drawNumber(renderFps, left, color, *canvas); + if (idle) { + drawDash(left, *canvas); + } else { + drawNumber(renderFps, left, color, *canvas); + } } left += 3 * (kDigitWidth + kDigitSpace); @@ -138,6 +147,14 @@ void RefreshRateOverlay::drawNumber(int number, int left, SkColor color, SkCanva SegmentDrawer::drawDigit(number % 10, left, color, canvas); } +void RefreshRateOverlay::drawDash(int left, SkCanvas& canvas) { + left += kDigitWidth + kDigitSpace; + SegmentDrawer::drawSegment(SegmentDrawer::Segment::Middle, left, SK_ColorRED, canvas); + + left += kDigitWidth + kDigitSpace; + SegmentDrawer::drawSegment(SegmentDrawer::Segment::Middle, left, SK_ColorRED, canvas); +} + std::unique_ptr RefreshRateOverlay::create(FpsRange range, ftl::Flags features) { std::unique_ptr overlay = @@ -171,7 +188,8 @@ bool RefreshRateOverlay::initCheck() const { return mSurfaceControl != nullptr; } -auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> const Buffers& { +auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps, bool idle) + -> const Buffers& { static const Buffers kNoBuffers; if (!mSurfaceControl) return kNoBuffers; @@ -197,8 +215,8 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> c createTransaction().setTransform(mSurfaceControl->get(), transform).apply(); - BufferCache::const_iterator it = - mBufferCache.find({refreshRate.getIntValue(), renderFps.getIntValue(), transformHint}); + BufferCache::const_iterator it = mBufferCache.find( + {refreshRate.getIntValue(), renderFps.getIntValue(), transformHint, idle}); if (it == mBufferCache.end()) { const int maxFps = mFpsRange.max.getIntValue(); @@ -222,10 +240,10 @@ auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps) -> c const SkColor color = colorBase.toSkColor(); - auto buffers = draw(refreshIntFps, renderIntFps, color, transformHint, mFeatures); + auto buffers = draw(refreshIntFps, renderIntFps, idle, color, transformHint, mFeatures); it = mBufferCache - .try_emplace({refreshIntFps, renderIntFps, transformHint}, std::move(buffers)) - .first; + .try_emplace({refreshIntFps, renderIntFps, transformHint, idle}, + std::move(buffers)).first; } return it->second; @@ -257,7 +275,15 @@ void RefreshRateOverlay::setLayerStack(ui::LayerStack stack) { void RefreshRateOverlay::changeRefreshRate(Fps refreshRate, Fps renderFps) { mRefreshRate = refreshRate; mRenderFps = renderFps; - const auto buffer = getOrCreateBuffers(refreshRate, renderFps)[mFrame]; + const auto buffer = getOrCreateBuffers(refreshRate, renderFps, mIsVrrIdle)[mFrame]; + createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); +} + +void RefreshRateOverlay::onVrrIdle(bool idle) { + mIsVrrIdle = idle; + if (!mRefreshRate || !mRenderFps) return; + + const auto buffer = getOrCreateBuffers(*mRefreshRate, *mRenderFps, mIsVrrIdle)[mFrame]; createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } @@ -265,7 +291,7 @@ void RefreshRateOverlay::changeRenderRate(Fps renderFps) { if (mFeatures.test(Features::RenderRate) && mRefreshRate && FlagManager::getInstance().misc1()) { mRenderFps = renderFps; - const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps)[mFrame]; + const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps, mIsVrrIdle)[mFrame]; createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); } } @@ -273,7 +299,7 @@ void RefreshRateOverlay::changeRenderRate(Fps renderFps) { void RefreshRateOverlay::animate() { if (!mFeatures.test(Features::Spinner) || !mRefreshRate) return; - const auto& buffers = getOrCreateBuffers(*mRefreshRate, *mRenderFps); + const auto& buffers = getOrCreateBuffers(*mRefreshRate, *mRenderFps, mIsVrrIdle); mFrame = (mFrame + 1) % buffers.size(); const auto buffer = buffers[mFrame]; createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply(); diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h index b2896f07dd..d8aa048747 100644 --- a/services/surfaceflinger/RefreshRateOverlay.h +++ b/services/surfaceflinger/RefreshRateOverlay.h @@ -57,6 +57,7 @@ public: void changeRenderRate(Fps); void animate(); bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); } + void onVrrIdle(bool idle); RefreshRateOverlay(ConstructorTag, FpsRange, ftl::Flags); @@ -65,11 +66,12 @@ private: using Buffers = std::vector>; - static Buffers draw(int refreshRate, int renderFps, SkColor, ui::Transform::RotationFlags, - ftl::Flags); + static Buffers draw(int refreshRate, int renderFps, bool idle, SkColor, + ui::Transform::RotationFlags, ftl::Flags); static void drawNumber(int number, int left, SkColor, SkCanvas&); + static void drawDash(int left, SkCanvas&); - const Buffers& getOrCreateBuffers(Fps, Fps); + const Buffers& getOrCreateBuffers(Fps, Fps, bool); SurfaceComposerClient::Transaction createTransaction() const; @@ -77,10 +79,11 @@ private: int refreshRate; int renderFps; ui::Transform::RotationFlags flags; + bool idle; bool operator==(Key other) const { return refreshRate == other.refreshRate && renderFps == other.renderFps && - flags == other.flags; + flags == other.flags && idle == other.idle; } }; @@ -89,6 +92,7 @@ private: std::optional mRefreshRate; std::optional mRenderFps; + bool mIsVrrIdle = false; size_t mFrame = 0; const FpsRange mFpsRange; // For color interpolation. diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h index 43cdb5ec41..f430526b76 100644 --- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h +++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h @@ -33,6 +33,7 @@ struct ISchedulerCallback { virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull, Fps renderRate) = 0; virtual void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) = 0; + virtual void vrrDisplayIdle(bool idle) = 0; protected: ~ISchedulerCallback() = default; diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp index 846727b1d5..dd86e4f426 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp @@ -1501,7 +1501,7 @@ void RefreshRateSelector::constructAvailableRefreshRates() { return str; }; ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(), - mIsVrrDevice); + mIsVrrDevice.load()); return frameRateModes; }; @@ -1511,7 +1511,6 @@ void RefreshRateSelector::constructAvailableRefreshRates() { } bool RefreshRateSelector::isVrrDevice() const { - std::lock_guard lock(mLock); return mIsVrrDevice; } diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h index 4f491d9986..6f9c146dea 100644 --- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h +++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h @@ -383,6 +383,7 @@ public: Callbacks platform; Callbacks kernel; + Callbacks vrr; }; void setIdleTimerCallbacks(IdleTimerCallbacks callbacks) EXCLUDES(mIdleTimerCallbacksMutex) { @@ -501,6 +502,9 @@ private: std::optional getIdleTimerCallbacks() const REQUIRES(mIdleTimerCallbacksMutex) { if (!mIdleTimerCallbacks) return {}; + + if (mIsVrrDevice) return mIdleTimerCallbacks->vrr; + return mConfig.kernelIdleTimerController.has_value() ? mIdleTimerCallbacks->kernel : mIdleTimerCallbacks->platform; } @@ -536,7 +540,7 @@ private: std::vector mAppRequestFrameRates GUARDED_BY(mLock); // Caches whether the device is VRR-compatible based on the active display mode. - bool mIsVrrDevice GUARDED_BY(mLock) = false; + std::atomic_bool mIsVrrDevice = false; Policy mDisplayManagerPolicy GUARDED_BY(mLock); std::optional mOverridePolicy GUARDED_BY(mLock); diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 26e11e5693..0ef61b9469 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -942,8 +942,9 @@ std::shared_ptr Scheduler::promotePacesetterDisplayLocked( {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, - .onExpired = - [this] { kernelIdleTimerCallback(TimerState::Expired); }}}); + .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}, + .vrr = {.onReset = [this] { mSchedulerCallback.vrrDisplayIdle(false); }, + .onExpired = [this] { mSchedulerCallback.vrrDisplayIdle(true); }}}); pacesetter.selectorPtr->startIdleTimer(); diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index b8dd34d4f8..4bdff29e32 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -7703,6 +7703,22 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) { })); } +void SurfaceFlinger::vrrDisplayIdle(bool idle) { + // Update the overlay on the main thread to avoid race conditions with + // RefreshRateSelector::getActiveMode + static_cast(mScheduler->schedule([=, this] { + const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()); + if (!display) { + ALOGW("%s: default display is null", __func__); + return; + } + if (!display->isRefreshRateOverlayEnabled()) return; + + display->onVrrIdle(idle); + mScheduler->scheduleFrame(); + })); +} + std::pair, std::chrono::milliseconds> SurfaceFlinger::getKernelIdleTimerProperties(PhysicalDisplayId displayId) { const bool isKernelIdleTimerHwcSupported = getHwComposer().getComposer()->isSupported( diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index ee541c4364..3a34e46e3f 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -703,6 +703,7 @@ private: Fps renderRate) override; void onCommitNotComposited(PhysicalDisplayId pacesetterDisplayId) override REQUIRES(kMainThreadContext); + void vrrDisplayIdle(bool idle) override; // ICEPowerCallback overrides: void notifyCpuLoadUp() override; diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h index dec5fa56ea..8f21cdbaa6 100644 --- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h +++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h @@ -31,6 +31,7 @@ struct SchedulerCallback final : ISchedulerCallback { MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull, Fps), (override)); MOCK_METHOD(void, onCommitNotComposited, (PhysicalDisplayId), (override)); + MOCK_METHOD(void, vrrDisplayIdle, (bool), (override)); }; struct NoOpSchedulerCallback final : ISchedulerCallback { @@ -41,6 +42,7 @@ struct NoOpSchedulerCallback final : ISchedulerCallback { void onChoreographerAttached() override {} void onExpectedPresentTimePosted(TimePoint, ftl::NonNull, Fps) override {} void onCommitNotComposited(PhysicalDisplayId) override {} + void vrrDisplayIdle(bool) override {} }; } // namespace android::scheduler::mock -- GitLab From c2d89d6538f3d79f5f2619bed4f751a12e0437e4 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 12 Jul 2024 14:35:00 -0400 Subject: [PATCH 447/465] SF: Remove misleading pacesetter choice fallback Since Id5cb29c3cbaa8ed455a15d8be3a32e79a470cce5, the pacesetter display is always specified to Scheduler::promotePacesetterDisplayLocked, so the fallback to pick the first display as the pacesetter (and std::optional plumbing) is dead code. Bug: 329450361 Flag: EXEMPT refactor Test: presubmit (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:16d1b3ce1588cc2d12ff72957f5807ead487e8e2) Merged-In: I831de5eef94eaefe0682f9c87ffe76acd50171b0 Change-Id: I831de5eef94eaefe0682f9c87ffe76acd50171b0 --- services/surfaceflinger/Scheduler/Scheduler.cpp | 16 ++++++++-------- services/surfaceflinger/Scheduler/Scheduler.h | 13 +++++-------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 0ef61b9469..9ea3f35057 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -117,10 +117,10 @@ void Scheduler::startTimers() { } } -void Scheduler::setPacesetterDisplay(std::optional pacesetterIdOpt) { +void Scheduler::setPacesetterDisplay(PhysicalDisplayId pacesetterId) { demotePacesetterDisplay(); - promotePacesetterDisplay(pacesetterIdOpt); + promotePacesetterDisplay(pacesetterId); } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, @@ -917,22 +917,22 @@ bool Scheduler::updateFrameRateOverridesLocked(GlobalSignals consideredSignals, return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides); } -void Scheduler::promotePacesetterDisplay(std::optional pacesetterIdOpt) { +void Scheduler::promotePacesetterDisplay(PhysicalDisplayId pacesetterId) { std::shared_ptr pacesetterVsyncSchedule; { std::scoped_lock lock(mDisplayLock); - pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterId); } applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } std::shared_ptr Scheduler::promotePacesetterDisplayLocked( - std::optional pacesetterIdOpt) { - // TODO(b/241286431): Choose the pacesetter display. - mPacesetterDisplayId = pacesetterIdOpt.value_or(mDisplays.begin()->first); - ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str()); + PhysicalDisplayId pacesetterId) { + // TODO: b/241286431 - Choose the pacesetter among mDisplays. + mPacesetterDisplayId = pacesetterId; + ALOGI("Display %s is the pacesetter", to_string(pacesetterId).c_str()); std::shared_ptr newVsyncSchedulePtr; if (const auto pacesetterOpt = pacesetterDisplayLocked()) { diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 1a4aa79818..4dba6fcfb1 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -92,8 +92,8 @@ public: void startTimers(); - // TODO(b/241285191): Remove this API by promoting pacesetter in onScreen{Acquired,Released}. - void setPacesetterDisplay(std::optional) REQUIRES(kMainThreadContext) + // TODO: b/241285191 - Remove this API by promoting pacesetter in onScreen{Acquired,Released}. + void setPacesetterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); using RefreshRateSelectorPtr = std::shared_ptr; @@ -377,10 +377,8 @@ private: void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock); void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); - // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified. - // The new `mPacesetterDisplayId` is never `std::nullopt`. - void promotePacesetterDisplay(std::optional pacesetterIdOpt = std::nullopt) - REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); + void promotePacesetterDisplay(PhysicalDisplayId pacesetterId) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock); // Changes to the displays (e.g. registering and unregistering) must be made // while mDisplayLock is locked, and the new pacesetter then must be promoted while @@ -388,8 +386,7 @@ private: // MessageQueue and EventThread need to use the new pacesetter's // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, // or else we may deadlock with EventThread. - std::shared_ptr promotePacesetterDisplayLocked( - std::optional pacesetterIdOpt = std::nullopt) + std::shared_ptr promotePacesetterDisplayLocked(PhysicalDisplayId pacesetterId) REQUIRES(kMainThreadContext, mDisplayLock); void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); -- GitLab From 3a3330644c3745cc195038fba6107b1d7b435ab9 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Fri, 12 Jul 2024 15:55:28 -0400 Subject: [PATCH 448/465] SF: Do not toggle idle timer on display hotplug As a short-term measure to avoid a deadlock during display hotplug, skip stopping and starting the pacesetter's idle timer on demotion/promotion. This assumes that hotplug results in demoting/promoting the same display as pacesetter, currently always the active internal display. Fixes: 329450361 Flag: com.android.graphics.surfaceflinger.flags.connected_display Test: Manual (foldable, connected display) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b972419619256ea07ebdb35b737946faf5c21bdc) Merged-In: Ieeb026666c8abdd14e0d4690a624fb60306b1bc1 Change-Id: Ieeb026666c8abdd14e0d4690a624fb60306b1bc1 --- .../surfaceflinger/Scheduler/Scheduler.cpp | 65 +++++++++++-------- services/surfaceflinger/Scheduler/Scheduler.h | 24 +++++-- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp index 9ea3f35057..5ec7e48332 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.cpp +++ b/services/surfaceflinger/Scheduler/Scheduler.cpp @@ -82,7 +82,7 @@ Scheduler::~Scheduler() { mTouchTimer.reset(); // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler. - demotePacesetterDisplay(); + demotePacesetterDisplay({.toggleIdleTimer = true}); } void Scheduler::initVsync(frametimeline::TokenManager& tokenManager, @@ -118,9 +118,10 @@ void Scheduler::startTimers() { } void Scheduler::setPacesetterDisplay(PhysicalDisplayId pacesetterId) { - demotePacesetterDisplay(); + constexpr PromotionParams kPromotionParams = {.toggleIdleTimer = true}; - promotePacesetterDisplay(pacesetterId); + demotePacesetterDisplay(kPromotionParams); + promotePacesetterDisplay(pacesetterId, kPromotionParams); } void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, @@ -139,16 +140,22 @@ void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr, VsyncSchedulePtr schedulePtr, PhysicalDisplayId activeDisplayId) { - demotePacesetterDisplay(); + const bool isPrimary = (ftl::FakeGuard(mDisplayLock), !mPacesetterDisplayId); - auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) { + // Start the idle timer for the first registered (i.e. primary) display. + const PromotionParams promotionParams = {.toggleIdleTimer = isPrimary}; + + demotePacesetterDisplay(promotionParams); + + auto [pacesetterVsyncSchedule, isNew] = [&]() REQUIRES(kMainThreadContext) { std::scoped_lock lock(mDisplayLock); const bool isNew = mDisplays .emplace_or_replace(displayId, displayId, std::move(selectorPtr), std::move(schedulePtr), mFeatures) .second; - return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId), isNew); + return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId, promotionParams), + isNew); }(); applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); @@ -166,7 +173,8 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId dispatchHotplug(displayId, Hotplug::Disconnected); - demotePacesetterDisplay(); + constexpr PromotionParams kPromotionParams = {.toggleIdleTimer = false}; + demotePacesetterDisplay(kPromotionParams); std::shared_ptr pacesetterVsyncSchedule; { @@ -178,7 +186,7 @@ void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId // headless virtual display.) LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!"); - pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId, kPromotionParams); } applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } @@ -917,19 +925,18 @@ bool Scheduler::updateFrameRateOverridesLocked(GlobalSignals consideredSignals, return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides); } -void Scheduler::promotePacesetterDisplay(PhysicalDisplayId pacesetterId) { +void Scheduler::promotePacesetterDisplay(PhysicalDisplayId pacesetterId, PromotionParams params) { std::shared_ptr pacesetterVsyncSchedule; - { std::scoped_lock lock(mDisplayLock); - pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterId); + pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterId, params); } applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule)); } std::shared_ptr Scheduler::promotePacesetterDisplayLocked( - PhysicalDisplayId pacesetterId) { + PhysicalDisplayId pacesetterId, PromotionParams params) { // TODO: b/241286431 - Choose the pacesetter among mDisplays. mPacesetterDisplayId = pacesetterId; ALOGI("Display %s is the pacesetter", to_string(pacesetterId).c_str()); @@ -938,15 +945,18 @@ std::shared_ptr Scheduler::promotePacesetterDisplayLocked( if (const auto pacesetterOpt = pacesetterDisplayLocked()) { const Display& pacesetter = *pacesetterOpt; - pacesetter.selectorPtr->setIdleTimerCallbacks( - {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, - .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, - .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, - .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}, - .vrr = {.onReset = [this] { mSchedulerCallback.vrrDisplayIdle(false); }, - .onExpired = [this] { mSchedulerCallback.vrrDisplayIdle(true); }}}); - - pacesetter.selectorPtr->startIdleTimer(); + if (!FlagManager::getInstance().connected_display() || params.toggleIdleTimer) { + pacesetter.selectorPtr->setIdleTimerCallbacks( + {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); }, + .onExpired = [this] { idleTimerCallback(TimerState::Expired); }}, + .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); }, + .onExpired = + [this] { kernelIdleTimerCallback(TimerState::Expired); }}, + .vrr = {.onReset = [this] { mSchedulerCallback.vrrDisplayIdle(false); }, + .onExpired = [this] { mSchedulerCallback.vrrDisplayIdle(true); }}}); + + pacesetter.selectorPtr->startIdleTimer(); + } newVsyncSchedulePtr = pacesetter.schedulePtr; @@ -966,11 +976,14 @@ void Scheduler::applyNewVsyncSchedule(std::shared_ptr vsyncSchedu } } -void Scheduler::demotePacesetterDisplay() { - // No need to lock for reads on kMainThreadContext. - if (const auto pacesetterPtr = FTL_FAKE_GUARD(mDisplayLock, pacesetterSelectorPtrLocked())) { - pacesetterPtr->stopIdleTimer(); - pacesetterPtr->clearIdleTimerCallbacks(); +void Scheduler::demotePacesetterDisplay(PromotionParams params) { + if (!FlagManager::getInstance().connected_display() || params.toggleIdleTimer) { + // No need to lock for reads on kMainThreadContext. + if (const auto pacesetterPtr = + FTL_FAKE_GUARD(mDisplayLock, pacesetterSelectorPtrLocked())) { + pacesetterPtr->stopIdleTimer(); + pacesetterPtr->clearIdleTimerCallbacks(); + } } // Clear state that depends on the pacesetter's RefreshRateSelector. diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h index 4dba6fcfb1..94583db5a8 100644 --- a/services/surfaceflinger/Scheduler/Scheduler.h +++ b/services/surfaceflinger/Scheduler/Scheduler.h @@ -377,8 +377,17 @@ private: void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock); void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod); - void promotePacesetterDisplay(PhysicalDisplayId pacesetterId) REQUIRES(kMainThreadContext) - EXCLUDES(mDisplayLock); + // TODO: b/241286431 - Remove this option, which assumes that the pacesetter does not change + // when a (secondary) display is registered or unregistered. In the short term, this avoids + // a deadlock where the main thread joins with the timer thread as the timer thread waits to + // lock a mutex held by the main thread. + struct PromotionParams { + // Whether to stop and start the idle timer. Ignored unless connected_display flag is set. + bool toggleIdleTimer; + }; + + void promotePacesetterDisplay(PhysicalDisplayId pacesetterId, PromotionParams) + REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock); // Changes to the displays (e.g. registering and unregistering) must be made // while mDisplayLock is locked, and the new pacesetter then must be promoted while @@ -386,13 +395,16 @@ private: // MessageQueue and EventThread need to use the new pacesetter's // VsyncSchedule, and this must happen while mDisplayLock is *not* locked, // or else we may deadlock with EventThread. - std::shared_ptr promotePacesetterDisplayLocked(PhysicalDisplayId pacesetterId) + std::shared_ptr promotePacesetterDisplayLocked(PhysicalDisplayId pacesetterId, + PromotionParams) REQUIRES(kMainThreadContext, mDisplayLock); void applyNewVsyncSchedule(std::shared_ptr) EXCLUDES(mDisplayLock); - // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by - // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit. - void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock); + // If toggleIdleTimer is true, the calling thread blocks until the pacesetter's idle timer + // thread exits, in which case mDisplayLock must not be locked by the caller to avoid deadlock, + // since the timer thread locks it before exit. + void demotePacesetterDisplay(PromotionParams) REQUIRES(kMainThreadContext) + EXCLUDES(mDisplayLock, mPolicyLock); void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr, PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext) -- GitLab From a515fd50aea0610749260045263ca6f6a03d9b86 Mon Sep 17 00:00:00 2001 From: Dominik Laskowski Date: Mon, 15 Jul 2024 08:24:49 -0400 Subject: [PATCH 449/465] SF: Restore mStateLock mutex for modesetting In order to fix a deadlock during display hotplug, the modesetting mutex was changed from mStateLock to a granular DisplayModeController lock, in the following CLs: I30ec756f134d2d67a70ac8797008dc792eac035e Iaae02d95e175e55f52f65da1481b64f1f533a04c (reverted) This exposed a data race leading to a crash, which, unlike the deadlock, affects all devices with refresh rate switching. The race will be fixed by: If6141d2928643e82b3251b321e18c300cd8c201c (WIP) As a stopgap until then, restore mStateLock (on top of the DMC lock) on the main thread to fix the race, but reintroduce the deadlock. The lock was already restored on the idle timer thread by the reverted CL above. Fixes: 348827779 Flag: com.android.graphics.surfaceflinger.flags.connected_display Test: Manual (foldable, connected display) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:b2fc4235635f5160ed5ae35c1fbd1fcc3e45c59f) Merged-In: Ie15de10d16eefeb65289574b120d2ef4cc09d6c0 Change-Id: Ie15de10d16eefeb65289574b120d2ef4cc09d6c0 --- services/surfaceflinger/SurfaceFlinger.cpp | 25 ++++++++++++++++------ services/surfaceflinger/SurfaceFlinger.h | 4 ++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 4bdff29e32..fcead9ff6e 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -1414,6 +1414,8 @@ status_t SurfaceFlinger::setActiveModeFromBackdoor(const spgetResolution(); oldResolution != activeMode.modePtr->getResolution()) { - Mutex::Autolock lock(mStateLock); + ConditionalLock lock(mStateLock, !FlagManager::getInstance().connected_display()); auto& state = mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId)); // We need to generate new sequenceId in order to recreate the display (and this @@ -1483,7 +1485,7 @@ void SurfaceFlinger::initiateDisplayModeChanges() { std::optional displayToUpdateImmediately; - for (const auto& [displayId, physical] : FTL_FAKE_GUARD(mStateLock, mPhysicalDisplays)) { + for (const auto& [displayId, physical] : mPhysicalDisplays) { auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId); if (!desiredModeOpt) { continue; @@ -2618,9 +2620,13 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, return false; } - for (const auto [displayId, _] : frameTargets) { - if (mDisplayModeController.isModeSetPending(displayId)) { - finalizeDisplayModeChange(displayId); + { + ConditionalLock lock(mStateLock, FlagManager::getInstance().connected_display()); + + for (const auto [displayId, _] : frameTargets) { + if (mDisplayModeController.isModeSetPending(displayId)) { + finalizeDisplayModeChange(displayId); + } } } @@ -2719,9 +2725,16 @@ bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId, ? &mLayerHierarchyBuilder.getHierarchy() : nullptr, updateAttachedChoreographer); + + if (FlagManager::getInstance().connected_display()) { + initiateDisplayModeChanges(); + } } - initiateDisplayModeChanges(); + if (!FlagManager::getInstance().connected_display()) { + ftl::FakeGuard guard(mStateLock); + initiateDisplayModeChanges(); + } updateCursorAsync(); if (!mustComposite) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 3a34e46e3f..a3534b582c 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -738,9 +738,9 @@ private: status_t setActiveModeFromBackdoor(const sp&, DisplayModeId, Fps minFps, Fps maxFps); - void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) EXCLUDES(mStateLock); + void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) REQUIRES(mStateLock); void finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext) - EXCLUDES(mStateLock); + REQUIRES(mStateLock); void dropModeRequest(PhysicalDisplayId) REQUIRES(kMainThreadContext); void applyActiveMode(PhysicalDisplayId) REQUIRES(kMainThreadContext); -- GitLab From bef25703de7472a32ba1d28e44f46041e81c7298 Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Mon, 8 Jul 2024 21:41:59 +0000 Subject: [PATCH 450/465] Use pixel stride of 2 for Y422_I's Y plane Y422_I's layout is: Y0 Cr0 Y1 Cb0 ... ...which means that successive luminance samples are separated by 2 bytes, not 1. Bug: 351311764 Test: builds Flag: EXEMPT bugfix (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:ff206d6d5d9150bbfeac0dfbe92825d97a7de5b2) Merged-In: I4c24aae97ef052f6385281dd6e73f748d880decb Change-Id: I4c24aae97ef052f6385281dd6e73f748d880decb --- libs/nativewindow/AHardwareBuffer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp index 52612870ab..dd78049b16 100644 --- a/libs/nativewindow/AHardwareBuffer.cpp +++ b/libs/nativewindow/AHardwareBuffer.cpp @@ -300,7 +300,9 @@ int AHardwareBuffer_lockPlanes(AHardwareBuffer* buffer, uint64_t usage, if (result == 0) { outPlanes->planeCount = 3; outPlanes->planes[0].data = yuvData.y; - if (format == AHARDWAREBUFFER_FORMAT_YCbCr_P010) { + // P010 is word-aligned 10-bit semiplaner, and YCbCr_422_I is a single interleaved plane + if (format == AHARDWAREBUFFER_FORMAT_YCbCr_P010 || + format == AHARDWAREBUFFER_FORMAT_YCbCr_422_I) { outPlanes->planes[0].pixelStride = 2; } else { outPlanes->planes[0].pixelStride = 1; -- GitLab From dd7c1cf29e5940d16b8293c5f758f344f021f00f Mon Sep 17 00:00:00 2001 From: Sungtak Lee Date: Tue, 30 Jul 2024 21:54:42 +0000 Subject: [PATCH 451/465] Add libgui bq_consumer_attach_callback flag Add a flag for enabling IProducerListener callback when the consumer attaches a buffer. Bug: 353202582 Flag: EXEMPT adding flag Test: n/a Merged-In: I3268cb82fdc7d194f8b1864b8a72147ff51e3973 Change-Id: Ieaa25ed05d04a33de47de7ee3296e4bf1dde7a26 --- libs/gui/libgui_flags.aconfig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig index 792966fd5e..87cef087db 100644 --- a/libs/gui/libgui_flags.aconfig +++ b/libs/gui/libgui_flags.aconfig @@ -9,6 +9,14 @@ flag { is_fixed_read_only: true } # bq_setframerate +flag { + name: "bq_consumer_attach_callback" + namespace: "core_graphics" + description: "Controls IProducerListener to have consumer side attach callback" + bug: "353202582" + is_fixed_read_only: true +} # bq_consumer_attach_callback + flag { name: "frametimestamps_previousrelease" namespace: "core_graphics" -- GitLab From 03ceb6d3ba3031e8f7f43eff9b56c0c1aecd5445 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Mon, 29 Jul 2024 18:02:13 +0000 Subject: [PATCH 452/465] SF: VsyncTimeline::isVSyncInPhase should use display rate [unflagged] Remove the flag for the fix in commit 4a719e88f8050713cc75d655f3e75473e5404327 so it will be enabled on 24Q3 release. Bug: 328352850 Bug: 355684882 Test: see b/355684882 (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:0e55f685fedaf3c215b646c92d567d722d95c02e) Merged-In: I02deca58bfd9a2cf169c2e58995cc42eeb854c8d Change-Id: I02deca58bfd9a2cf169c2e58995cc42eeb854c8d --- services/surfaceflinger/Scheduler/VSyncPredictor.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp index dd3c4b074c..1422cfa1d5 100644 --- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp +++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp @@ -737,9 +737,7 @@ bool VSyncPredictor::VsyncTimeline::isVSyncInPhase(Model model, nsecs_t vsync, F return ticks(TimePoint::fromNs(timePoint) - now); }; - Fps displayFps = !FlagManager::getInstance().vrr_bugfix_24q4() && mRenderRateOpt - ? *mRenderRateOpt - : Fps::fromPeriodNsecs(mIdealPeriod.ns()); + Fps displayFps = Fps::fromPeriodNsecs(mIdealPeriod.ns()); const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate); const auto now = TimePoint::now(); -- GitLab From 488cfaca2363c50ee7369b4a69f281038809f3b2 Mon Sep 17 00:00:00 2001 From: Sungtak Lee Date: Tue, 30 Jul 2024 21:54:42 +0000 Subject: [PATCH 453/465] Add libgui bq_consumer_attach_callback flag Add a flag for enabling IProducerListener callback when the consumer attaches a buffer. Bug: 353202582 Flag: EXEMPT adding flag Test: n/a (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:dd7c1cf29e5940d16b8293c5f758f344f021f00f) Merged-In: Ieaa25ed05d04a33de47de7ee3296e4bf1dde7a26 Change-Id: Ieaa25ed05d04a33de47de7ee3296e4bf1dde7a26 --- libs/gui/libgui_flags.aconfig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig index 792966fd5e..87cef087db 100644 --- a/libs/gui/libgui_flags.aconfig +++ b/libs/gui/libgui_flags.aconfig @@ -9,6 +9,14 @@ flag { is_fixed_read_only: true } # bq_setframerate +flag { + name: "bq_consumer_attach_callback" + namespace: "core_graphics" + description: "Controls IProducerListener to have consumer side attach callback" + bug: "353202582" + is_fixed_read_only: true +} # bq_consumer_attach_callback + flag { name: "frametimestamps_previousrelease" namespace: "core_graphics" -- GitLab From 9cfc94c803a343d409e4c7f60043264479e55c4b Mon Sep 17 00:00:00 2001 From: Sungtak Lee Date: Wed, 31 Jul 2024 19:35:07 +0000 Subject: [PATCH 454/465] Add IProducerListener::onBufferAttached Add a new callback to IProducerListener for buffer being attached to the consumer. BYPASS_IGBP_IGBC_API_REASON=approved new code in libgui Bug: 353202582 Flag: com.android.graphics.libgui.flags.bq_consumer_attach_callback (cherry picked from https://android-review.googlesource.com/q/commit:6d223cbd07f0582ac99e4b607f6983284638aa0b) Merged-In: I21f9d4925ab6c8985ab349514bc0198e61d1de23 Change-Id: I21f9d4925ab6c8985ab349514bc0198e61d1de23 --- libs/gui/BufferQueueConsumer.cpp | 147 +++++++++++++---------- libs/gui/BufferQueueCore.cpp | 1 + libs/gui/BufferQueueProducer.cpp | 3 + libs/gui/IProducerListener.cpp | 56 +++++++++ libs/gui/include/gui/BufferQueueCore.h | 4 + libs/gui/include/gui/IProducerListener.h | 12 ++ libs/gui/tests/BufferQueue_test.cpp | 85 +++++++++++++ 7 files changed, 243 insertions(+), 65 deletions(-) diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp index 11f5174d76..69d25be006 100644 --- a/libs/gui/BufferQueueConsumer.cpp +++ b/libs/gui/BufferQueueConsumer.cpp @@ -42,6 +42,8 @@ #include +#include + namespace android { // Macros for include BufferQueueCore information in log messages @@ -370,79 +372,94 @@ status_t BufferQueueConsumer::attachBuffer(int* outSlot, return BAD_VALUE; } - std::lock_guard lock(mCore->mMutex); + sp listener; + { + std::lock_guard lock(mCore->mMutex); - if (mCore->mSharedBufferMode) { - BQ_LOGE("attachBuffer: cannot attach a buffer in shared buffer mode"); - return BAD_VALUE; - } + if (mCore->mSharedBufferMode) { + BQ_LOGE("attachBuffer: cannot attach a buffer in shared buffer mode"); + return BAD_VALUE; + } - // Make sure we don't have too many acquired buffers - int numAcquiredBuffers = 0; - for (int s : mCore->mActiveBuffers) { - if (mSlots[s].mBufferState.isAcquired()) { - ++numAcquiredBuffers; + // Make sure we don't have too many acquired buffers + int numAcquiredBuffers = 0; + for (int s : mCore->mActiveBuffers) { + if (mSlots[s].mBufferState.isAcquired()) { + ++numAcquiredBuffers; + } } - } - if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) { - BQ_LOGE("attachBuffer: max acquired buffer count reached: %d " - "(max %d)", numAcquiredBuffers, - mCore->mMaxAcquiredBufferCount); - return INVALID_OPERATION; - } + if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) { + BQ_LOGE("attachBuffer: max acquired buffer count reached: %d " + "(max %d)", numAcquiredBuffers, + mCore->mMaxAcquiredBufferCount); + return INVALID_OPERATION; + } - if (buffer->getGenerationNumber() != mCore->mGenerationNumber) { - BQ_LOGE("attachBuffer: generation number mismatch [buffer %u] " - "[queue %u]", buffer->getGenerationNumber(), - mCore->mGenerationNumber); - return BAD_VALUE; - } + if (buffer->getGenerationNumber() != mCore->mGenerationNumber) { + BQ_LOGE("attachBuffer: generation number mismatch [buffer %u] " + "[queue %u]", buffer->getGenerationNumber(), + mCore->mGenerationNumber); + return BAD_VALUE; + } - // Find a free slot to put the buffer into - int found = BufferQueueCore::INVALID_BUFFER_SLOT; - if (!mCore->mFreeSlots.empty()) { - auto slot = mCore->mFreeSlots.begin(); - found = *slot; - mCore->mFreeSlots.erase(slot); - } else if (!mCore->mFreeBuffers.empty()) { - found = mCore->mFreeBuffers.front(); - mCore->mFreeBuffers.remove(found); - } - if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { - BQ_LOGE("attachBuffer: could not find free buffer slot"); - return NO_MEMORY; + // Find a free slot to put the buffer into + int found = BufferQueueCore::INVALID_BUFFER_SLOT; + if (!mCore->mFreeSlots.empty()) { + auto slot = mCore->mFreeSlots.begin(); + found = *slot; + mCore->mFreeSlots.erase(slot); + } else if (!mCore->mFreeBuffers.empty()) { + found = mCore->mFreeBuffers.front(); + mCore->mFreeBuffers.remove(found); + } + if (found == BufferQueueCore::INVALID_BUFFER_SLOT) { + BQ_LOGE("attachBuffer: could not find free buffer slot"); + return NO_MEMORY; + } + +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) + if (mCore->mBufferAttachedCbEnabled) { + listener = mCore->mConnectedProducerListener; + } +#endif + + mCore->mActiveBuffers.insert(found); + *outSlot = found; + ATRACE_BUFFER_INDEX(*outSlot); + BQ_LOGV("attachBuffer: returning slot %d", *outSlot); + + mSlots[*outSlot].mGraphicBuffer = buffer; + mSlots[*outSlot].mBufferState.attachConsumer(); + mSlots[*outSlot].mNeedsReallocation = true; + mSlots[*outSlot].mFence = Fence::NO_FENCE; + mSlots[*outSlot].mFrameNumber = 0; + + // mAcquireCalled tells BufferQueue that it doesn't need to send a valid + // GraphicBuffer pointer on the next acquireBuffer call, which decreases + // Binder traffic by not un/flattening the GraphicBuffer. However, it + // requires that the consumer maintain a cached copy of the slot <--> buffer + // mappings, which is why the consumer doesn't need the valid pointer on + // acquire. + // + // The StreamSplitter is one of the primary users of the attach/detach + // logic, and while it is running, all buffers it acquires are immediately + // detached, and all buffers it eventually releases are ones that were + // attached (as opposed to having been obtained from acquireBuffer), so it + // doesn't make sense to maintain the slot/buffer mappings, which would + // become invalid for every buffer during detach/attach. By setting this to + // false, the valid GraphicBuffer pointer will always be sent with acquire + // for attached buffers. + mSlots[*outSlot].mAcquireCalled = false; + + VALIDATE_CONSISTENCY(); } - mCore->mActiveBuffers.insert(found); - *outSlot = found; - ATRACE_BUFFER_INDEX(*outSlot); - BQ_LOGV("attachBuffer: returning slot %d", *outSlot); - - mSlots[*outSlot].mGraphicBuffer = buffer; - mSlots[*outSlot].mBufferState.attachConsumer(); - mSlots[*outSlot].mNeedsReallocation = true; - mSlots[*outSlot].mFence = Fence::NO_FENCE; - mSlots[*outSlot].mFrameNumber = 0; - - // mAcquireCalled tells BufferQueue that it doesn't need to send a valid - // GraphicBuffer pointer on the next acquireBuffer call, which decreases - // Binder traffic by not un/flattening the GraphicBuffer. However, it - // requires that the consumer maintain a cached copy of the slot <--> buffer - // mappings, which is why the consumer doesn't need the valid pointer on - // acquire. - // - // The StreamSplitter is one of the primary users of the attach/detach - // logic, and while it is running, all buffers it acquires are immediately - // detached, and all buffers it eventually releases are ones that were - // attached (as opposed to having been obtained from acquireBuffer), so it - // doesn't make sense to maintain the slot/buffer mappings, which would - // become invalid for every buffer during detach/attach. By setting this to - // false, the valid GraphicBuffer pointer will always be sent with acquire - // for attached buffers. - mSlots[*outSlot].mAcquireCalled = false; - - VALIDATE_CONSISTENCY(); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) + if (listener != nullptr) { + listener->onBufferAttached(); + } +#endif return NO_ERROR; } diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp index 648db67fc1..e0c5b1f7d1 100644 --- a/libs/gui/BufferQueueCore.cpp +++ b/libs/gui/BufferQueueCore.cpp @@ -96,6 +96,7 @@ BufferQueueCore::BufferQueueCore() mLinkedToDeath(), mConnectedProducerListener(), mBufferReleasedCbEnabled(false), + mBufferAttachedCbEnabled(false), mSlots(), mQueue(), mFreeSlots(), diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp index 69345a971e..a4d105d320 100644 --- a/libs/gui/BufferQueueProducer.cpp +++ b/libs/gui/BufferQueueProducer.cpp @@ -1360,6 +1360,9 @@ status_t BufferQueueProducer::connect(const sp& listener, #endif mCore->mConnectedProducerListener = listener; mCore->mBufferReleasedCbEnabled = listener->needsReleaseNotify(); +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) + mCore->mBufferAttachedCbEnabled = listener->needsAttachNotify(); +#endif } break; default: diff --git a/libs/gui/IProducerListener.cpp b/libs/gui/IProducerListener.cpp index 0683087211..7700795e1d 100644 --- a/libs/gui/IProducerListener.cpp +++ b/libs/gui/IProducerListener.cpp @@ -25,6 +25,9 @@ enum { ON_BUFFER_RELEASED = IBinder::FIRST_CALL_TRANSACTION, NEEDS_RELEASE_NOTIFY, ON_BUFFERS_DISCARDED, + ON_BUFFER_DETACHED, + ON_BUFFER_ATTACHED, + NEEDS_ATTACH_NOTIFY, }; class BpProducerListener : public BpInterface @@ -64,6 +67,38 @@ public: data.writeInt32Vector(discardedSlots); remote()->transact(ON_BUFFERS_DISCARDED, data, &reply, IBinder::FLAG_ONEWAY); } + +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) + virtual void onBufferDetached(int slot) { + Parcel data, reply; + data.writeInterfaceToken(IProducerListener::getInterfaceDescriptor()); + data.writeInt32(slot); + remote()->transact(ON_BUFFER_DETACHED, data, &reply, IBinder::FLAG_ONEWAY); + } + + virtual void onBufferAttached() { + Parcel data, reply; + data.writeInterfaceToken(IProducerListener::getInterfaceDescriptor()); + remote()->transact(ON_BUFFER_ATTACHED, data, &reply, IBinder::FLAG_ONEWAY); + } + + virtual bool needsAttachNotify() { + bool result; + Parcel data, reply; + data.writeInterfaceToken(IProducerListener::getInterfaceDescriptor()); + status_t err = remote()->transact(NEEDS_ATTACH_NOTIFY, data, &reply); + if (err != NO_ERROR) { + ALOGE("IProducerListener: binder call \'needsAttachNotify\' failed"); + return true; + } + err = reply.readBool(&result); + if (err != NO_ERROR) { + ALOGE("IProducerListener: malformed binder reply"); + return true; + } + return result; + } +#endif }; // Out-of-line virtual method definition to trigger vtable emission in this @@ -115,6 +150,27 @@ status_t BnProducerListener::onTransact(uint32_t code, const Parcel& data, onBuffersDiscarded(discardedSlots); return NO_ERROR; } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) + case ON_BUFFER_DETACHED: { + CHECK_INTERFACE(IProducerListener, data, reply); + int slot; + status_t result = data.readInt32(&slot); + if (result != NO_ERROR) { + ALOGE("ON_BUFFER_DETACHED failed to read slot: %d", result); + return result; + } + onBufferDetached(slot); + return NO_ERROR; + } + case ON_BUFFER_ATTACHED: + CHECK_INTERFACE(IProducerListener, data, reply); + onBufferAttached(); + return NO_ERROR; + case NEEDS_ATTACH_NOTIFY: + CHECK_INTERFACE(IProducerListener, data, reply); + reply->writeBool(needsAttachNotify()); + return NO_ERROR; +#endif } return BBinder::onTransact(code, data, reply, flags); } diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h index bb52c8ec88..d5dd7c897c 100644 --- a/libs/gui/include/gui/BufferQueueCore.h +++ b/libs/gui/include/gui/BufferQueueCore.h @@ -192,6 +192,10 @@ private: // callback is registered by the listener. When set to false, // mConnectedProducerListener will not trigger onBufferReleased() callback. bool mBufferReleasedCbEnabled; + // mBufferAttachedCbEnabled is used to indicate whether onBufferAttached() + // callback is registered by the listener. When set to false, + // mConnectedProducerListener will not trigger onBufferAttached() callback. + bool mBufferAttachedCbEnabled; // mSlots is an array of buffer slots that must be mirrored on the producer // side. This allows buffer ownership to be transferred between the producer diff --git a/libs/gui/include/gui/IProducerListener.h b/libs/gui/include/gui/IProducerListener.h index b15f501518..3dcc6b6670 100644 --- a/libs/gui/include/gui/IProducerListener.h +++ b/libs/gui/include/gui/IProducerListener.h @@ -25,6 +25,8 @@ #include #include +#include + namespace android { // ProducerListener is the interface through which the BufferQueue notifies the @@ -55,6 +57,16 @@ public: // This is called without any lock held and can be called concurrently by // multiple threads. virtual void onBufferDetached(int /*slot*/) {} // Asynchronous +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) + // onBufferAttached is called from IGraphicBufferConsumer::attachBuffer to + // notify the producer that a buffer is attached. + // + // This is called without any lock held and can be called concurrently by + // multiple threads. This callback is enabled only when needsAttachNotify() + // returns {@code true}. + virtual void onBufferAttached() {} // Asynchronous + virtual bool needsAttachNotify() { return false; } +#endif }; #ifndef NO_BINDER diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp index 272c5ed2b4..590e2c87c9 100644 --- a/libs/gui/tests/BufferQueue_test.cpp +++ b/libs/gui/tests/BufferQueue_test.cpp @@ -1262,6 +1262,91 @@ TEST_F(BufferQueueTest, TestConsumerDetachProducerListener) { ASSERT_TRUE(result == WOULD_BLOCK || result == TIMED_OUT || result == INVALID_OPERATION); } +#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK) +struct BufferAttachedListener : public BnProducerListener { +public: + BufferAttachedListener(bool enable) : mEnabled(enable), mAttached(0) {} + virtual ~BufferAttachedListener() = default; + + virtual void onBufferReleased() {} + virtual bool needsReleaseNotify() { return true; } + virtual void onBufferAttached() { + ++mAttached; + } + virtual bool needsAttachNotify() { return mEnabled; } + + int getNumAttached() const { return mAttached; } +private: + const bool mEnabled; + int mAttached; +}; + +TEST_F(BufferQueueTest, TestConsumerAttachProducerListener) { + createBufferQueue(); + sp mc1(new MockConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(mc1, true)); + IGraphicBufferProducer::QueueBufferOutput output; + // Do not enable attach callback. + sp pl1(new BufferAttachedListener(false)); + ASSERT_EQ(OK, mProducer->connect(pl1, NATIVE_WINDOW_API_CPU, true, &output)); + ASSERT_EQ(OK, mProducer->setDequeueTimeout(0)); + ASSERT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1)); + + sp fence = Fence::NO_FENCE; + sp buffer = nullptr; + + int slot; + status_t result = OK; + + ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(1)); + + result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, + GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr); + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + ASSERT_EQ(OK, mProducer->detachBuffer(slot)); + + // Check # of attach is zero. + ASSERT_EQ(0, pl1->getNumAttached()); + + // Attach a buffer and check the callback was not called. + ASSERT_EQ(OK, mConsumer->attachBuffer(&slot, buffer)); + ASSERT_EQ(0, pl1->getNumAttached()); + + mProducer = nullptr; + mConsumer = nullptr; + createBufferQueue(); + + sp mc2(new MockConsumer); + ASSERT_EQ(OK, mConsumer->consumerConnect(mc2, true)); + // Enable attach callback. + sp pl2(new BufferAttachedListener(true)); + ASSERT_EQ(OK, mProducer->connect(pl2, NATIVE_WINDOW_API_CPU, true, &output)); + ASSERT_EQ(OK, mProducer->setDequeueTimeout(0)); + ASSERT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1)); + + fence = Fence::NO_FENCE; + buffer = nullptr; + + result = OK; + + ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(1)); + + result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, + GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr); + ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result); + ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer)); + ASSERT_EQ(OK, mProducer->detachBuffer(slot)); + + // Check # of attach is zero. + ASSERT_EQ(0, pl2->getNumAttached()); + + // Attach a buffer and check the callback was called. + ASSERT_EQ(OK, mConsumer->attachBuffer(&slot, buffer)); + ASSERT_EQ(1, pl2->getNumAttached()); +} +#endif + TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) { createBufferQueue(); sp mc(new MockConsumer); -- GitLab From 76a4212fb43aa1f6f34e5a03addc5a932defe649 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 5 Aug 2024 14:42:56 +0000 Subject: [PATCH 455/465] Mock InputDevice in the InputMapper unit tests This makes the InputMapperUnitTest subclasses easier to test, as the tests will not have to rely on the behavior of actual InputDevice implementation. Unfortunately, this means InputDevice must now be a virtual class to allow its mocked subclass to define its behavior for the methods that we want to change. In this CL, we take the approach of only marking the methods that we need to customize as virtual to avoid the overhead of vtable lookups at runtime. An alternative approach would be to introduce a type parameter to InputDeviceContext to allow us to specify a different InputDevice implementation in the tests. This would eliminate any addtional runtime overheads resulting from vtable lookups. However, this would mean every InputMapper class would need to have this new type parameter, adding verbosity and complextity to the codebase. For this reason, the virtual member approach was preferred. Bug: 354270482 Bug: 353128452 Test: atest inputflinger_tests Flag: EXEMPT refactor (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:31d05c4c78e7924e4bb6dc45092e612a9c7d5bfb) Merged-In: Id7e103e1f04812c92ee8ecfdfe1e75a9949efa9e Change-Id: Id7e103e1f04812c92ee8ecfdfe1e75a9949efa9e --- .../inputflinger/reader/include/InputDevice.h | 6 +- .../tests/CursorInputMapper_test.cpp | 7 -- .../inputflinger/tests/InputMapperTest.cpp | 13 ++-- services/inputflinger/tests/InputMapperTest.h | 9 +-- services/inputflinger/tests/InterfaceMocks.h | 76 ++++++++++++++++++- .../tests/KeyboardInputMapper_test.cpp | 1 - .../tests/MultiTouchInputMapper_test.cpp | 1 - .../MultiTouchMotionAccumulator_test.cpp | 5 +- .../tests/TouchpadInputMapper_test.cpp | 1 - 9 files changed, 84 insertions(+), 35 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 2a7e262bf5..200c8787d9 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -43,7 +43,7 @@ class InputDevice { public: InputDevice(InputReaderContext* context, int32_t id, int32_t generation, const InputDeviceIdentifier& identifier); - ~InputDevice(); + virtual ~InputDevice(); inline InputReaderContext* getContext() { return mContext; } inline int32_t getId() const { return mId; } @@ -56,7 +56,7 @@ public: } inline const std::string getLocation() const { return mIdentifier.location; } inline ftl::Flags getClasses() const { return mClasses; } - inline uint32_t getSources() const { return mSources; } + inline virtual uint32_t getSources() const { return mSources; } inline bool hasEventHubDevices() const { return !mDevices.empty(); } inline bool isExternal() { return mIsExternal; } @@ -132,7 +132,7 @@ public: [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when); - inline const PropertyMap& getConfiguration() { return mConfiguration; } + inline virtual const PropertyMap& getConfiguration() const { return mConfiguration; } inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } std::optional getAssociatedDisplayId(); diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index 83074ff899..cda067f03e 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -157,7 +157,6 @@ protected: } void createMapper() { - createDevice(); mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); } @@ -535,7 +534,6 @@ TEST_F(CursorInputMapperUnitTest, ProcessShouldNotRotateMotionsWhenOrientationAw // need to be rotated. mPropertyMap.addProperty("cursor.mode", "navigation"); mPropertyMap.addProperty("cursor.orientationAware", "1"); - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -553,7 +551,6 @@ TEST_F(CursorInputMapperUnitTest, ProcessShouldRotateMotionsWhenNotOrientationAw // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. mPropertyMap.addProperty("cursor.mode", "navigation"); - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -645,7 +642,6 @@ TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) { mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); // Set up the secondary display as the display on which the pointer should be shown. // The InputDevice is not associated with any display. - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -666,7 +662,6 @@ TEST_F(CursorInputMapperUnitTest, DisplayViewport secondaryViewport = createSecondaryViewport(); mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); // Set up the primary display as the display on which the pointer should be shown. - createDevice(); // Associate the InputDevice with the secondary display. ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -959,7 +954,6 @@ TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationWithAsso mPropertyMap.addProperty("cursor.mode", "pointer"); DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0); mReaderConfiguration.setDisplayViewports({primaryViewport}); - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -997,7 +991,6 @@ TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationOnDispla mReaderConfiguration.setDisplayViewports({primaryViewport}); // Disable acceleration for the display. mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID); - createDevice(); // Don't associate the device with the display yet. ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index b5c9232a78..ff0de83fb3 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -26,7 +26,9 @@ namespace android { using testing::_; +using testing::NiceMock; using testing::Return; +using testing::ReturnRef; void InputMapperUnitTest::SetUpWithBus(int bus) { mFakePolicy = sp::make(); @@ -43,16 +45,11 @@ void InputMapperUnitTest::SetUpWithBus(int bus) { EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) { return mPropertyMap; }); -} -void InputMapperUnitTest::createDevice() { - mDevice = std::make_unique(&mMockInputReaderContext, DEVICE_ID, - /*generation=*/2, mIdentifier); - mDevice->addEmptyEventHubDevice(EVENTHUB_ID); + mDevice = std::make_unique>(&mMockInputReaderContext, DEVICE_ID, + /*generation=*/2, mIdentifier); + ON_CALL((*mDevice), getConfiguration).WillByDefault(ReturnRef(mPropertyMap)); mDeviceContext = std::make_unique(*mDevice, EVENTHUB_ID); - std::list args = - mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{}); - ASSERT_THAT(args, testing::ElementsAre(testing::VariantWith(_))); } void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max, diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 5bd8cda976..870e11bd04 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -43,13 +43,6 @@ protected: virtual void SetUp() override { SetUpWithBus(0); } virtual void SetUpWithBus(int bus); - /** - * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of - * mPropertyMap, so tests that need to set configuration properties should do so before calling - * this. Others will most likely want to call it in their SetUp method. - */ - void createDevice(); - void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution); void expectScanCodes(bool present, std::set scanCodes); @@ -65,7 +58,7 @@ protected: MockEventHubInterface mMockEventHub; sp mFakePolicy; MockInputReaderContext mMockInputReaderContext; - std::unique_ptr mDevice; + std::unique_ptr mDevice; std::unique_ptr mDeviceContext; InputReaderConfiguration mReaderConfiguration; diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index 16d3193908..8a15d077c6 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -59,7 +60,7 @@ public: MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override)); int32_t bumpGeneration() override { return ++mGeneration; } - MOCK_METHOD(void, getExternalStylusDevices, (std::vector & outDevices), + MOCK_METHOD(void, getExternalStylusDevices, (std::vector& outDevices), (override)); MOCK_METHOD(std::list, dispatchExternalStylusState, (const StylusState& outState), (override)); @@ -172,7 +173,7 @@ public: MOCK_METHOD(void, requestReopenDevices, (), (override)); MOCK_METHOD(void, wake, (), (override)); - MOCK_METHOD(void, dump, (std::string & dump), (const, override)); + MOCK_METHOD(void, dump, (std::string& dump), (const, override)); MOCK_METHOD(void, monitor, (), (const, override)); MOCK_METHOD(bool, isDeviceEnabled, (int32_t deviceId), (const, override)); MOCK_METHOD(status_t, enableDevice, (int32_t deviceId), (override)); @@ -189,4 +190,75 @@ public: MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override)); }; +class MockInputDevice : public InputDevice { +public: + MockInputDevice(InputReaderContext* context, int32_t id, int32_t generation, + const InputDeviceIdentifier& identifier) + : InputDevice(context, id, generation, identifier) {} + + MOCK_METHOD(uint32_t, getSources, (), (const, override)); + MOCK_METHOD(bool, isEnabled, (), ()); + + MOCK_METHOD(void, dump, (std::string& dump, const std::string& eventHubDevStr), ()); + MOCK_METHOD(void, addEmptyEventHubDevice, (int32_t eventHubId), ()); + MOCK_METHOD(std::list, addEventHubDevice, + (nsecs_t when, int32_t eventHubId, const InputReaderConfiguration& readerConfig), + ()); + MOCK_METHOD(void, removeEventHubDevice, (int32_t eventHubId), ()); + MOCK_METHOD(std::list, configure, + (nsecs_t when, const InputReaderConfiguration& readerConfig, + ConfigurationChanges changes), + ()); + MOCK_METHOD(std::list, reset, (nsecs_t when), ()); + MOCK_METHOD(std::list, process, (const RawEvent* rawEvents, size_t count), ()); + MOCK_METHOD(std::list, timeoutExpired, (nsecs_t when), ()); + MOCK_METHOD(std::list, updateExternalStylusState, (const StylusState& state), ()); + + MOCK_METHOD(InputDeviceInfo, getDeviceInfo, (), ()); + MOCK_METHOD(int32_t, getKeyCodeState, (uint32_t sourceMask, int32_t keyCode), ()); + MOCK_METHOD(int32_t, getScanCodeState, (uint32_t sourceMask, int32_t scanCode), ()); + MOCK_METHOD(int32_t, getSwitchState, (uint32_t sourceMask, int32_t switchCode), ()); + MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t locationKeyCode), (const)); + MOCK_METHOD(bool, markSupportedKeyCodes, + (uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags), ()); + MOCK_METHOD(std::list, vibrate, + (const VibrationSequence& sequence, ssize_t repeat, int32_t token), ()); + MOCK_METHOD(std::list, cancelVibrate, (int32_t token), ()); + MOCK_METHOD(bool, isVibrating, (), ()); + MOCK_METHOD(std::vector, getVibratorIds, (), ()); + MOCK_METHOD(std::list, cancelTouch, (nsecs_t when, nsecs_t readTime), ()); + MOCK_METHOD(bool, enableSensor, + (InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, + std::chrono::microseconds maxBatchReportLatency), + ()); + + MOCK_METHOD(void, disableSensor, (InputDeviceSensorType sensorType), ()); + MOCK_METHOD(void, flushSensor, (InputDeviceSensorType sensorType), ()); + + MOCK_METHOD(std::optional, getBatteryEventHubId, (), (const)); + + MOCK_METHOD(bool, setLightColor, (int32_t lightId, int32_t color), ()); + MOCK_METHOD(bool, setLightPlayerId, (int32_t lightId, int32_t playerId), ()); + MOCK_METHOD(std::optional, getLightColor, (int32_t lightId), ()); + MOCK_METHOD(std::optional, getLightPlayerId, (int32_t lightId), ()); + + MOCK_METHOD(int32_t, getMetaState, (), ()); + MOCK_METHOD(void, updateMetaState, (int32_t keyCode), ()); + + MOCK_METHOD(void, addKeyRemapping, (int32_t fromKeyCode, int32_t toKeyCode), ()); + + MOCK_METHOD(void, setKeyboardType, (KeyboardType keyboardType), ()); + + MOCK_METHOD(void, bumpGeneration, (), ()); + + MOCK_METHOD(const PropertyMap&, getConfiguration, (), (const, override)); + + MOCK_METHOD(NotifyDeviceResetArgs, notifyReset, (nsecs_t when), ()); + + MOCK_METHOD(std::optional, getAssociatedDisplayId, (), ()); + + MOCK_METHOD(void, updateLedState, (bool reset), ()); + + MOCK_METHOD(size_t, getMapperCount, (), ()); +}; } // namespace android diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index ab47cc67b1..e9ab13a666 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -55,7 +55,6 @@ protected: void SetUp() override { InputMapperUnitTest::SetUp(); - createDevice(); // set key-codes expected in tests for (const auto& [scanCode, outKeycode] : mKeyCodeMap) { diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp index b5f897154b..c57c251b38 100644 --- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -112,7 +112,6 @@ protected: mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); - createDevice(); mMapper = createInputMapper(*mDeviceContext, mFakePolicy->getReaderConfiguration()); } diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp index b441a23803..9ddb8c138d 100644 --- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp +++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp @@ -23,10 +23,7 @@ class MultiTouchMotionAccumulatorTest : public InputMapperUnitTest { protected: static constexpr size_t SLOT_COUNT = 8; - void SetUp() override { - InputMapperUnitTest::SetUp(); - createDevice(); - } + void SetUp() override { InputMapperUnitTest::SetUp(); } MultiTouchMotionAccumulator mMotionAccumulator; diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index 2b62dd13ef..12fa835e8c 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -112,7 +112,6 @@ protected: .WillRepeatedly([]() -> base::Result> { return base::ResultError("Axis not supported", NAME_NOT_FOUND); }); - createDevice(); mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); } }; -- GitLab From 34f357038f54efe73e463c97614143a13f1c4990 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 23 Jul 2024 21:59:36 +0000 Subject: [PATCH 456/465] InputReader: Use shared keyboard source for all key events When multiple EventHub devices are merged into a single InputDevice, it's possible that there are more than one KeyboardInputMappers created for the device. In this case, each mapper could be configured with a different source. This can lead to situations where a device generates key events that have different sources, depending on the mapper from which it originated. To make sure all key events use a consistent source for each InputDevice, use the shared keyboard source when generating events from a KeyboardInputMapper. Bug: 354270482 Test: atest inputflinger_tests Flag: EXEMPT bugfix (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:38636652797f16d84f8b5672be4d7d95d8b31947) Merged-In: I586424b5e8d31d17cbe635d9f91a889aee906d40 Change-Id: I586424b5e8d31d17cbe635d9f91a889aee906d40 --- .../inputflinger/reader/include/InputDevice.h | 1 + .../reader/mapper/KeyboardInputMapper.cpp | 30 ++++++++----- .../reader/mapper/KeyboardInputMapper.h | 6 ++- services/inputflinger/tests/InputMapperTest.h | 9 ++-- .../inputflinger/tests/InputReader_test.cpp | 45 +++++++++++++++++++ .../tests/KeyboardInputMapper_test.cpp | 2 + 6 files changed, 78 insertions(+), 15 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 200c8787d9..4374ff5438 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -299,6 +299,7 @@ public: inline ftl::Flags getDeviceClasses() const { return mEventHub->getDeviceClasses(mId); } + inline uint32_t getDeviceSources() const { return mDevice.getSources(); } inline InputDeviceIdentifier getDeviceIdentifier() const { return mEventHub->getDeviceIdentifier(mId); } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 25f4893baf..8eb67306c4 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -98,10 +98,10 @@ static bool isMediaKey(int32_t keyCode) { KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, uint32_t source) - : InputMapper(deviceContext, readerConfig), mSource(source) {} + : InputMapper(deviceContext, readerConfig), mMapperSource(source) {} uint32_t KeyboardInputMapper::getSources() const { - return mSource; + return mMapperSource; } ui::Rotation KeyboardInputMapper::getOrientation() { @@ -351,8 +351,8 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } - out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId, mSource, - getDisplayId(), policyFlags, + out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId, + getEventSource(), getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags, keyCode, scanCode, keyMetaState, downTime)); return out; @@ -478,12 +478,12 @@ std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { std::list out; size_t n = mKeyDowns.size(); for (size_t i = 0; i < n; i++) { - out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, - systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource, - getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP, - mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED, - mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, - mKeyDowns[i].downTime)); + out.emplace_back( + NotifyKeyArgs(getContext()->getNextId(), when, systemTime(SYSTEM_TIME_MONOTONIC), + getDeviceId(), getEventSource(), getDisplayId(), /*policyFlags=*/0, + AKEY_EVENT_ACTION_UP, mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED, + mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, + mKeyDowns[i].downTime)); } mKeyDowns.clear(); mMetaState = AMETA_NONE; @@ -501,4 +501,14 @@ void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) { } } +uint32_t KeyboardInputMapper::getEventSource() const { + // For all input events generated by this mapper, use the source that's shared across all + // KeyboardInputMappers for this device in case there are more than one. + static constexpr auto ALL_KEYBOARD_SOURCES = + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD; + const auto deviceSources = getDeviceContext().getDeviceSources(); + LOG_ALWAYS_FATAL_IF((deviceSources & mMapperSource) != mMapperSource); + return deviceSources & ALL_KEYBOARD_SOURCES; +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index c7df558caf..2df0b85e21 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -60,7 +60,10 @@ private: int32_t flags{}; }; - uint32_t mSource{}; + // The keyboard source for this mapper. Events generated should use the source shared + // by all KeyboardInputMappers for this input device. + uint32_t mMapperSource{}; + std::optional mKeyboardLayoutInfo; std::vector mKeyDowns{}; // keys that are down @@ -106,6 +109,7 @@ private: std::optional findViewport(const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list cancelAllDownKeys(nsecs_t when); void onKeyDownProcessed(nsecs_t downTime); + uint32_t getEventSource() const; }; } // namespace android diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 870e11bd04..4271a700fa 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -114,11 +114,12 @@ protected: T& constructAndAddMapper(Args... args) { // ensure a device entry exists for this eventHubId mDevice->addEmptyEventHubDevice(EVENTHUB_ID); - // configure the empty device - configureDevice(/*changes=*/{}); - return mDevice->constructAndAddMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), - args...); + auto& mapper = + mDevice->constructAndAddMapper(EVENTHUB_ID, + mFakePolicy->getReaderConfiguration(), args...); + configureDevice(/*changes=*/{}); + return mapper; } void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index fe238f3087..e6367b7303 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -4179,6 +4179,51 @@ TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) { ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags); } +/** + * When there is more than one KeyboardInputMapper for an InputDevice, each mapper should produce + * events that use the shared keyboard source across all mappers. This is to ensure that each + * input device generates key events in a consistent manner, regardless of which mapper produces + * the event. + */ +TEST_F(KeyboardInputMapperTest, UsesSharedKeyboardSource) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); + + // Add a mapper with SOURCE_KEYBOARD + KeyboardInputMapper& keyboardMapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); + + process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD))); + process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD))); + + // Add a mapper with SOURCE_DPAD + KeyboardInputMapper& dpadMapper = + constructAndAddMapper(AINPUT_SOURCE_DPAD); + for (auto* mapper : {&keyboardMapper, &dpadMapper}) { + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD))); + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD))); + } + + // Add a mapper with SOURCE_GAMEPAD + KeyboardInputMapper& gamepadMapper = + constructAndAddMapper(AINPUT_SOURCE_GAMEPAD); + for (auto* mapper : {&keyboardMapper, &dpadMapper, &gamepadMapper}) { + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD))); + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD))); + } +} + // --- KeyboardInputMapperTest_ExternalAlphabeticDevice --- class KeyboardInputMapperTest_ExternalAlphabeticDevice : public InputMapperTest { diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index e9ab13a666..4d322e90ef 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -65,6 +65,8 @@ protected: mFakePolicy = sp::make(); EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get())); + ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD)); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration, AINPUT_SOURCE_KEYBOARD); } -- GitLab From 449810708018aefd84df0175dc377f13278d4d14 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Tue, 23 Jul 2024 21:59:36 +0000 Subject: [PATCH 457/465] InputReader: Use shared keyboard source for all key events When multiple EventHub devices are merged into a single InputDevice, it's possible that there are more than one KeyboardInputMappers created for the device. In this case, each mapper could be configured with a different source. This can lead to situations where a device generates key events that have different sources, depending on the mapper from which it originated. To make sure all key events use a consistent source for each InputDevice, use the shared keyboard source when generating events from a KeyboardInputMapper. Bug: 354270482 Test: atest inputflinger_tests Flag: EXEMPT bugfix (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:38636652797f16d84f8b5672be4d7d95d8b31947) Merged-In: I586424b5e8d31d17cbe635d9f91a889aee906d40 Change-Id: I586424b5e8d31d17cbe635d9f91a889aee906d40 --- .../inputflinger/reader/include/InputDevice.h | 1 + .../reader/mapper/KeyboardInputMapper.cpp | 30 ++++++++----- .../reader/mapper/KeyboardInputMapper.h | 6 ++- services/inputflinger/tests/InputMapperTest.h | 9 ++-- .../inputflinger/tests/InputReader_test.cpp | 45 +++++++++++++++++++ .../tests/KeyboardInputMapper_test.cpp | 2 + 6 files changed, 78 insertions(+), 15 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index 2a7e262bf5..c55499d0ab 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -299,6 +299,7 @@ public: inline ftl::Flags getDeviceClasses() const { return mEventHub->getDeviceClasses(mId); } + inline uint32_t getDeviceSources() const { return mDevice.getSources(); } inline InputDeviceIdentifier getDeviceIdentifier() const { return mEventHub->getDeviceIdentifier(mId); } diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp index 25f4893baf..8eb67306c4 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp @@ -98,10 +98,10 @@ static bool isMediaKey(int32_t keyCode) { KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig, uint32_t source) - : InputMapper(deviceContext, readerConfig), mSource(source) {} + : InputMapper(deviceContext, readerConfig), mMapperSource(source) {} uint32_t KeyboardInputMapper::getSources() const { - return mSource; + return mMapperSource; } ui::Rotation KeyboardInputMapper::getOrientation() { @@ -351,8 +351,8 @@ std::list KeyboardInputMapper::processKey(nsecs_t when, nsecs_t read policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } - out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId, mSource, - getDisplayId(), policyFlags, + out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId, + getEventSource(), getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags, keyCode, scanCode, keyMetaState, downTime)); return out; @@ -478,12 +478,12 @@ std::list KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) { std::list out; size_t n = mKeyDowns.size(); for (size_t i = 0; i < n; i++) { - out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, - systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource, - getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP, - mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED, - mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, - mKeyDowns[i].downTime)); + out.emplace_back( + NotifyKeyArgs(getContext()->getNextId(), when, systemTime(SYSTEM_TIME_MONOTONIC), + getDeviceId(), getEventSource(), getDisplayId(), /*policyFlags=*/0, + AKEY_EVENT_ACTION_UP, mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED, + mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE, + mKeyDowns[i].downTime)); } mKeyDowns.clear(); mMetaState = AMETA_NONE; @@ -501,4 +501,14 @@ void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) { } } +uint32_t KeyboardInputMapper::getEventSource() const { + // For all input events generated by this mapper, use the source that's shared across all + // KeyboardInputMappers for this device in case there are more than one. + static constexpr auto ALL_KEYBOARD_SOURCES = + AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD; + const auto deviceSources = getDeviceContext().getDeviceSources(); + LOG_ALWAYS_FATAL_IF((deviceSources & mMapperSource) != mMapperSource); + return deviceSources & ALL_KEYBOARD_SOURCES; +} + } // namespace android diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h index c7df558caf..2df0b85e21 100644 --- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h +++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h @@ -60,7 +60,10 @@ private: int32_t flags{}; }; - uint32_t mSource{}; + // The keyboard source for this mapper. Events generated should use the source shared + // by all KeyboardInputMappers for this input device. + uint32_t mMapperSource{}; + std::optional mKeyboardLayoutInfo; std::vector mKeyDowns{}; // keys that are down @@ -106,6 +109,7 @@ private: std::optional findViewport(const InputReaderConfiguration& readerConfig); [[nodiscard]] std::list cancelAllDownKeys(nsecs_t when); void onKeyDownProcessed(nsecs_t downTime); + uint32_t getEventSource() const; }; } // namespace android diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 5bd8cda976..59ce93ab4b 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -121,11 +121,12 @@ protected: T& constructAndAddMapper(Args... args) { // ensure a device entry exists for this eventHubId mDevice->addEmptyEventHubDevice(EVENTHUB_ID); - // configure the empty device - configureDevice(/*changes=*/{}); - return mDevice->constructAndAddMapper(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), - args...); + auto& mapper = + mDevice->constructAndAddMapper(EVENTHUB_ID, + mFakePolicy->getReaderConfiguration(), args...); + configureDevice(/*changes=*/{}); + return mapper; } void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height, diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index fe238f3087..e6367b7303 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -4179,6 +4179,51 @@ TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) { ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags); } +/** + * When there is more than one KeyboardInputMapper for an InputDevice, each mapper should produce + * events that use the shared keyboard source across all mappers. This is to ensure that each + * input device generates key events in a consistent manner, regardless of which mapper produces + * the event. + */ +TEST_F(KeyboardInputMapperTest, UsesSharedKeyboardSource) { + mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE); + + // Add a mapper with SOURCE_KEYBOARD + KeyboardInputMapper& keyboardMapper = + constructAndAddMapper(AINPUT_SOURCE_KEYBOARD); + + process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD))); + process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE( + mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD))); + + // Add a mapper with SOURCE_DPAD + KeyboardInputMapper& dpadMapper = + constructAndAddMapper(AINPUT_SOURCE_DPAD); + for (auto* mapper : {&keyboardMapper, &dpadMapper}) { + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD))); + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD))); + } + + // Add a mapper with SOURCE_GAMEPAD + KeyboardInputMapper& gamepadMapper = + constructAndAddMapper(AINPUT_SOURCE_GAMEPAD); + for (auto* mapper : {&keyboardMapper, &dpadMapper, &gamepadMapper}) { + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD))); + process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0); + ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled( + WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD))); + } +} + // --- KeyboardInputMapperTest_ExternalAlphabeticDevice --- class KeyboardInputMapperTest_ExternalAlphabeticDevice : public InputMapperTest { diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index ab47cc67b1..a74cad38d5 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -66,6 +66,8 @@ protected: mFakePolicy = sp::make(); EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get())); + ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD)); + mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration, AINPUT_SOURCE_KEYBOARD); } -- GitLab From 20007465c7ff61740fd27b1fa9454ee55de9bda4 Mon Sep 17 00:00:00 2001 From: Prabir Pradhan Date: Mon, 5 Aug 2024 14:42:56 +0000 Subject: [PATCH 458/465] Mock InputDevice in the InputMapper unit tests This makes the InputMapperUnitTest subclasses easier to test, as the tests will not have to rely on the behavior of actual InputDevice implementation. Unfortunately, this means InputDevice must now be a virtual class to allow its mocked subclass to define its behavior for the methods that we want to change. In this CL, we take the approach of only marking the methods that we need to customize as virtual to avoid the overhead of vtable lookups at runtime. An alternative approach would be to introduce a type parameter to InputDeviceContext to allow us to specify a different InputDevice implementation in the tests. This would eliminate any addtional runtime overheads resulting from vtable lookups. However, this would mean every InputMapper class would need to have this new type parameter, adding verbosity and complextity to the codebase. For this reason, the virtual member approach was preferred. Bug: 354270482 Bug: 353128452 Test: atest inputflinger_tests Flag: EXEMPT refactor (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:31d05c4c78e7924e4bb6dc45092e612a9c7d5bfb) (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:76a4212fb43aa1f6f34e5a03addc5a932defe649) Merged-In: Id7e103e1f04812c92ee8ecfdfe1e75a9949efa9e Change-Id: Id7e103e1f04812c92ee8ecfdfe1e75a9949efa9e --- .../inputflinger/reader/include/InputDevice.h | 6 +- .../tests/CursorInputMapper_test.cpp | 7 -- .../inputflinger/tests/InputMapperTest.cpp | 13 ++-- services/inputflinger/tests/InputMapperTest.h | 9 +-- services/inputflinger/tests/InterfaceMocks.h | 76 ++++++++++++++++++- .../tests/KeyboardInputMapper_test.cpp | 1 - .../tests/MultiTouchInputMapper_test.cpp | 1 - .../MultiTouchMotionAccumulator_test.cpp | 5 +- .../tests/TouchpadInputMapper_test.cpp | 1 - 9 files changed, 84 insertions(+), 35 deletions(-) diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h index c55499d0ab..4374ff5438 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -43,7 +43,7 @@ class InputDevice { public: InputDevice(InputReaderContext* context, int32_t id, int32_t generation, const InputDeviceIdentifier& identifier); - ~InputDevice(); + virtual ~InputDevice(); inline InputReaderContext* getContext() { return mContext; } inline int32_t getId() const { return mId; } @@ -56,7 +56,7 @@ public: } inline const std::string getLocation() const { return mIdentifier.location; } inline ftl::Flags getClasses() const { return mClasses; } - inline uint32_t getSources() const { return mSources; } + inline virtual uint32_t getSources() const { return mSources; } inline bool hasEventHubDevices() const { return !mDevices.empty(); } inline bool isExternal() { return mIsExternal; } @@ -132,7 +132,7 @@ public: [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when); - inline const PropertyMap& getConfiguration() { return mConfiguration; } + inline virtual const PropertyMap& getConfiguration() const { return mConfiguration; } inline EventHubInterface* getEventHub() { return mContext->getEventHub(); } std::optional getAssociatedDisplayId(); diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp index 83074ff899..cda067f03e 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -157,7 +157,6 @@ protected: } void createMapper() { - createDevice(); mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); } @@ -535,7 +534,6 @@ TEST_F(CursorInputMapperUnitTest, ProcessShouldNotRotateMotionsWhenOrientationAw // need to be rotated. mPropertyMap.addProperty("cursor.mode", "navigation"); mPropertyMap.addProperty("cursor.orientationAware", "1"); - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -553,7 +551,6 @@ TEST_F(CursorInputMapperUnitTest, ProcessShouldRotateMotionsWhenNotOrientationAw // Since InputReader works in the un-rotated coordinate space, only devices that are not // orientation-aware are affected by display rotation. mPropertyMap.addProperty("cursor.mode", "navigation"); - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -645,7 +642,6 @@ TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) { mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); // Set up the secondary display as the display on which the pointer should be shown. // The InputDevice is not associated with any display. - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -666,7 +662,6 @@ TEST_F(CursorInputMapperUnitTest, DisplayViewport secondaryViewport = createSecondaryViewport(); mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport}); // Set up the primary display as the display on which the pointer should be shown. - createDevice(); // Associate the InputDevice with the secondary display. ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -959,7 +954,6 @@ TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationWithAsso mPropertyMap.addProperty("cursor.mode", "pointer"); DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0); mReaderConfiguration.setDisplayViewports({primaryViewport}); - createDevice(); ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport); mMapper = createInputMapper(deviceContext, mReaderConfiguration); @@ -997,7 +991,6 @@ TEST_F(CursorInputMapperUnitTestWithNewBallistics, ConfigureAccelerationOnDispla mReaderConfiguration.setDisplayViewports({primaryViewport}); // Disable acceleration for the display. mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID); - createDevice(); // Don't associate the device with the display yet. ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp index b5c9232a78..ff0de83fb3 100644 --- a/services/inputflinger/tests/InputMapperTest.cpp +++ b/services/inputflinger/tests/InputMapperTest.cpp @@ -26,7 +26,9 @@ namespace android { using testing::_; +using testing::NiceMock; using testing::Return; +using testing::ReturnRef; void InputMapperUnitTest::SetUpWithBus(int bus) { mFakePolicy = sp::make(); @@ -43,16 +45,11 @@ void InputMapperUnitTest::SetUpWithBus(int bus) { EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) { return mPropertyMap; }); -} -void InputMapperUnitTest::createDevice() { - mDevice = std::make_unique(&mMockInputReaderContext, DEVICE_ID, - /*generation=*/2, mIdentifier); - mDevice->addEmptyEventHubDevice(EVENTHUB_ID); + mDevice = std::make_unique>(&mMockInputReaderContext, DEVICE_ID, + /*generation=*/2, mIdentifier); + ON_CALL((*mDevice), getConfiguration).WillByDefault(ReturnRef(mPropertyMap)); mDeviceContext = std::make_unique(*mDevice, EVENTHUB_ID); - std::list args = - mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{}); - ASSERT_THAT(args, testing::ElementsAre(testing::VariantWith(_))); } void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max, diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h index 59ce93ab4b..4271a700fa 100644 --- a/services/inputflinger/tests/InputMapperTest.h +++ b/services/inputflinger/tests/InputMapperTest.h @@ -43,13 +43,6 @@ protected: virtual void SetUp() override { SetUpWithBus(0); } virtual void SetUpWithBus(int bus); - /** - * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of - * mPropertyMap, so tests that need to set configuration properties should do so before calling - * this. Others will most likely want to call it in their SetUp method. - */ - void createDevice(); - void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution); void expectScanCodes(bool present, std::set scanCodes); @@ -65,7 +58,7 @@ protected: MockEventHubInterface mMockEventHub; sp mFakePolicy; MockInputReaderContext mMockInputReaderContext; - std::unique_ptr mDevice; + std::unique_ptr mDevice; std::unique_ptr mDeviceContext; InputReaderConfiguration mReaderConfiguration; diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h index 16d3193908..8a15d077c6 100644 --- a/services/inputflinger/tests/InterfaceMocks.h +++ b/services/inputflinger/tests/InterfaceMocks.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -59,7 +60,7 @@ public: MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override)); int32_t bumpGeneration() override { return ++mGeneration; } - MOCK_METHOD(void, getExternalStylusDevices, (std::vector & outDevices), + MOCK_METHOD(void, getExternalStylusDevices, (std::vector& outDevices), (override)); MOCK_METHOD(std::list, dispatchExternalStylusState, (const StylusState& outState), (override)); @@ -172,7 +173,7 @@ public: MOCK_METHOD(void, requestReopenDevices, (), (override)); MOCK_METHOD(void, wake, (), (override)); - MOCK_METHOD(void, dump, (std::string & dump), (const, override)); + MOCK_METHOD(void, dump, (std::string& dump), (const, override)); MOCK_METHOD(void, monitor, (), (const, override)); MOCK_METHOD(bool, isDeviceEnabled, (int32_t deviceId), (const, override)); MOCK_METHOD(status_t, enableDevice, (int32_t deviceId), (override)); @@ -189,4 +190,75 @@ public: MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override)); }; +class MockInputDevice : public InputDevice { +public: + MockInputDevice(InputReaderContext* context, int32_t id, int32_t generation, + const InputDeviceIdentifier& identifier) + : InputDevice(context, id, generation, identifier) {} + + MOCK_METHOD(uint32_t, getSources, (), (const, override)); + MOCK_METHOD(bool, isEnabled, (), ()); + + MOCK_METHOD(void, dump, (std::string& dump, const std::string& eventHubDevStr), ()); + MOCK_METHOD(void, addEmptyEventHubDevice, (int32_t eventHubId), ()); + MOCK_METHOD(std::list, addEventHubDevice, + (nsecs_t when, int32_t eventHubId, const InputReaderConfiguration& readerConfig), + ()); + MOCK_METHOD(void, removeEventHubDevice, (int32_t eventHubId), ()); + MOCK_METHOD(std::list, configure, + (nsecs_t when, const InputReaderConfiguration& readerConfig, + ConfigurationChanges changes), + ()); + MOCK_METHOD(std::list, reset, (nsecs_t when), ()); + MOCK_METHOD(std::list, process, (const RawEvent* rawEvents, size_t count), ()); + MOCK_METHOD(std::list, timeoutExpired, (nsecs_t when), ()); + MOCK_METHOD(std::list, updateExternalStylusState, (const StylusState& state), ()); + + MOCK_METHOD(InputDeviceInfo, getDeviceInfo, (), ()); + MOCK_METHOD(int32_t, getKeyCodeState, (uint32_t sourceMask, int32_t keyCode), ()); + MOCK_METHOD(int32_t, getScanCodeState, (uint32_t sourceMask, int32_t scanCode), ()); + MOCK_METHOD(int32_t, getSwitchState, (uint32_t sourceMask, int32_t switchCode), ()); + MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t locationKeyCode), (const)); + MOCK_METHOD(bool, markSupportedKeyCodes, + (uint32_t sourceMask, const std::vector& keyCodes, uint8_t* outFlags), ()); + MOCK_METHOD(std::list, vibrate, + (const VibrationSequence& sequence, ssize_t repeat, int32_t token), ()); + MOCK_METHOD(std::list, cancelVibrate, (int32_t token), ()); + MOCK_METHOD(bool, isVibrating, (), ()); + MOCK_METHOD(std::vector, getVibratorIds, (), ()); + MOCK_METHOD(std::list, cancelTouch, (nsecs_t when, nsecs_t readTime), ()); + MOCK_METHOD(bool, enableSensor, + (InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod, + std::chrono::microseconds maxBatchReportLatency), + ()); + + MOCK_METHOD(void, disableSensor, (InputDeviceSensorType sensorType), ()); + MOCK_METHOD(void, flushSensor, (InputDeviceSensorType sensorType), ()); + + MOCK_METHOD(std::optional, getBatteryEventHubId, (), (const)); + + MOCK_METHOD(bool, setLightColor, (int32_t lightId, int32_t color), ()); + MOCK_METHOD(bool, setLightPlayerId, (int32_t lightId, int32_t playerId), ()); + MOCK_METHOD(std::optional, getLightColor, (int32_t lightId), ()); + MOCK_METHOD(std::optional, getLightPlayerId, (int32_t lightId), ()); + + MOCK_METHOD(int32_t, getMetaState, (), ()); + MOCK_METHOD(void, updateMetaState, (int32_t keyCode), ()); + + MOCK_METHOD(void, addKeyRemapping, (int32_t fromKeyCode, int32_t toKeyCode), ()); + + MOCK_METHOD(void, setKeyboardType, (KeyboardType keyboardType), ()); + + MOCK_METHOD(void, bumpGeneration, (), ()); + + MOCK_METHOD(const PropertyMap&, getConfiguration, (), (const, override)); + + MOCK_METHOD(NotifyDeviceResetArgs, notifyReset, (nsecs_t when), ()); + + MOCK_METHOD(std::optional, getAssociatedDisplayId, (), ()); + + MOCK_METHOD(void, updateLedState, (bool reset), ()); + + MOCK_METHOD(size_t, getMapperCount, (), ()); +}; } // namespace android diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp index a74cad38d5..4d322e90ef 100644 --- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp +++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp @@ -55,7 +55,6 @@ protected: void SetUp() override { InputMapperUnitTest::SetUp(); - createDevice(); // set key-codes expected in tests for (const auto& [scanCode, outKeycode] : mKeyCodeMap) { diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp index b5f897154b..c57c251b38 100644 --- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp +++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp @@ -112,7 +112,6 @@ protected: mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0, /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL); - createDevice(); mMapper = createInputMapper(*mDeviceContext, mFakePolicy->getReaderConfiguration()); } diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp index b441a23803..9ddb8c138d 100644 --- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp +++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp @@ -23,10 +23,7 @@ class MultiTouchMotionAccumulatorTest : public InputMapperUnitTest { protected: static constexpr size_t SLOT_COUNT = 8; - void SetUp() override { - InputMapperUnitTest::SetUp(); - createDevice(); - } + void SetUp() override { InputMapperUnitTest::SetUp(); } MultiTouchMotionAccumulator mMotionAccumulator; diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp index 2b62dd13ef..12fa835e8c 100644 --- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp +++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp @@ -112,7 +112,6 @@ protected: .WillRepeatedly([]() -> base::Result> { return base::ResultError("Axis not supported", NAME_NOT_FOUND); }); - createDevice(); mMapper = createInputMapper(*mDeviceContext, mReaderConfiguration); } }; -- GitLab From bd5c270b2d7a2bb1b3ab6b8749c064842bd96da3 Mon Sep 17 00:00:00 2001 From: Ryan Prichard Date: Wed, 7 Aug 2024 14:40:14 -0700 Subject: [PATCH 459/465] inputflinger: avoid vector A container of const T uses std::allocator, which was an undocumented libc++ extension that has been removed. See llvm.org/PR96319. Bug: http://b/349681543 Test: m inputflinger Change-Id: I4ea703abd6bfcec5b361b638ee1a63c852854b94 --- services/inputflinger/dispatcher/trace/InputTracer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h index cb525a4a92..96e619c10d 100644 --- a/services/inputflinger/dispatcher/trace/InputTracer.h +++ b/services/inputflinger/dispatcher/trace/InputTracer.h @@ -65,10 +65,10 @@ private: void onEventProcessingComplete(nsecs_t processingTimestamp); InputTracer& tracer; - std::vector events; + std::vector events; bool isEventProcessingComplete{false}; // A queue to hold dispatch args from being traced until event processing is complete. - std::vector pendingDispatchArgs; + std::vector pendingDispatchArgs; // The metadata should not be modified after event processing is complete. TracedEventMetadata metadata{}; }; -- GitLab From ba6818ccba3780b2e0aab5b484c6c2513c2628df Mon Sep 17 00:00:00 2001 From: Devin Moore Date: Wed, 14 Aug 2024 22:32:07 +0000 Subject: [PATCH 460/465] Change binderRpcTest to use 'activity' for a Java test service The service just needs to exist on all devices that this test runs on. We recently hit an issue with 'batteryproperties' not existing. The 'activity' service is likely on more devices. Test: atest binderRpcTest Bug: 359783809 (cherry picked from https://android-review.googlesource.com/q/commit:29db860bfa17b7017de8464a262e7081b3b71112) Merged-In: I71fea4daeb929971add9eefa96e3d48f815e2212 Change-Id: I71fea4daeb929971add9eefa96e3d48f815e2212 --- libs/binder/tests/binderRpcTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp index 3efd84f145..d88048b96d 100644 --- a/libs/binder/tests/binderRpcTest.cpp +++ b/libs/binder/tests/binderRpcTest.cpp @@ -1377,8 +1377,8 @@ TEST(BinderRpc, Java) { sp sm = defaultServiceManager(); ASSERT_NE(nullptr, sm); // Any Java service with non-empty getInterfaceDescriptor() would do. - // Let's pick batteryproperties. - auto binder = sm->checkService(String16("batteryproperties")); + // Let's pick activity. + auto binder = sm->checkService(String16("activity")); ASSERT_NE(nullptr, binder); auto descriptor = binder->getInterfaceDescriptor(); ASSERT_GE(descriptor.size(), 0u); -- GitLab From b0f043a3e8192aeaae4760c9620f4944f7512f15 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 12 Aug 2024 23:57:22 +0000 Subject: [PATCH 461/465] Fix C compatibility in new API. I fixed all these in AOSP, but this API isn't in AOSP yet and is also missing the typedef needed for C compat. Also removed the duplicate decl, and the unnecessary __INTRODUCED_IN that either does nothing for types, or is actively harmful (pretty sure it does nothing), and the accompanying incorrect "introduced in" docs. Flag: no Bug: None Test: treehugger Change-Id: I9432f1146288dcb691e5f317887791cc94b120fe (cherry picked from commit 61118b30f38afcada2f165d70ae87dc5d4a24424) --- include/android/surface_control_input_receiver.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h index bdc52490d5..f0503f6324 100644 --- a/include/android/surface_control_input_receiver.h +++ b/include/android/surface_control_input_receiver.h @@ -59,17 +59,13 @@ typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context, AInputEvent *_Nonnull keyEvent) __INTRODUCED_IN(__ANDROID_API_V__); -struct AInputReceiverCallbacks; - -struct AInputReceiver; +typedef struct AInputReceiverCallbacks AInputReceiverCallbacks; /** * The InputReceiver that holds the reference to the registered input channel. This must be released * using AInputReceiver_release - * - * Available since API level 35. */ -typedef struct AInputReceiver AInputReceiver __INTRODUCED_IN(__ANDROID_API_V__); +typedef struct AInputReceiver AInputReceiver; /** * Registers an input receiver for an ASurfaceControl that will receive batched input event. For -- GitLab From 7e84455e4c8a0b751c4524ebfed8f0c12c9f29a0 Mon Sep 17 00:00:00 2001 From: Vishnu Nair Date: Sat, 29 Jun 2024 01:17:02 +0000 Subject: [PATCH 462/465] Enable single hop screenshots for only threaded re Fixes a deadlock where screenshot requests are blocked on the main thread which inturn is blocked by the screenshot request finishing. Flag: EXEMPT bug fix Bug: 349776684 Bug: 349741485 Bug: 352267188 Test: presubmit (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:5aadd249f326b21e5ef6cadfebc15b6a0a016816) Merged-In: Ibf038ad6db3e87c84508d3e101ca1eb144836d7c Change-Id: Ibf038ad6db3e87c84508d3e101ca1eb144836d7c --- services/surfaceflinger/RegionSamplingThread.cpp | 2 +- services/surfaceflinger/SurfaceFlinger.cpp | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp index 5add290e96..2ad70bb169 100644 --- a/services/surfaceflinger/RegionSamplingThread.cpp +++ b/services/surfaceflinger/RegionSamplingThread.cpp @@ -356,7 +356,7 @@ void RegionSamplingThread::captureSample() { FenceResult fenceResult; if (FlagManager::getInstance().single_hop_screenshot() && - FlagManager::getInstance().ce_fence_promise()) { + FlagManager::getInstance().ce_fence_promise() && mFlinger.mRenderEngine->isThreaded()) { std::vector> layerFEs; auto displayState = mFlinger.getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 596ec12d9e..abab49da96 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -8184,7 +8184,7 @@ void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuil } if (FlagManager::getInstance().single_hop_screenshot() && - FlagManager::getInstance().ce_fence_promise()) { + FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) { std::vector> layerFEs; auto displayState = getDisplayAndLayerSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, @@ -8558,10 +8558,8 @@ ftl::SharedFuture SurfaceFlinger::renderScreenImpl( // to CompositionEngine::present. ftl::SharedFuture presentFuture; if (FlagManager::getInstance().single_hop_screenshot() && - FlagManager::getInstance().ce_fence_promise()) { - presentFuture = mRenderEngine->isThreaded() - ? ftl::yield(present()).share() - : mScheduler->schedule(std::move(present)).share(); + FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) { + presentFuture = ftl::yield(present()).share(); } else { presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share() : ftl::yield(present()).share(); -- GitLab From 73e9ae304000410d942d13e733ddc1ab303fdfd3 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Tue, 20 Aug 2024 17:54:56 +0000 Subject: [PATCH 463/465] DO NOT MERGE Extend mPreviousPresentFences for high refresh rate To provide hardware enough time to display a frame under high refresh rate with long sf-duration, we extend mPreviousPresentFences by storing extra slots. We check proper present fence at different moment according to vsync period and sf-duration. Bug: 241193992 Bug: 361005063 Change-Id: Id44127cb9391799b17834cff0b2e637f273ee2d1 --- .../include/scheduler/FrameTargeter.h | 3 ++- .../Scheduler/src/FrameTargeter.cpp | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h index d37d2dc51b..638a6fb464 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h @@ -106,7 +106,8 @@ protected: FenceTimePtr fenceTime = FenceTime::NO_FENCE; TimePoint expectedPresentTime = TimePoint(); }; - std::array mPresentFences; + // size should be longest sf-duration / shortest vsync period and round up + std::array mPresentFences; // currently consider 166hz. utils::RingBuffer mFenceWithFenceTimes; TimePoint mLastSignaledFrameTime; diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp index badd21ef86..063f1b3d1d 100644 --- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp +++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp @@ -20,6 +20,20 @@ #include #include +namespace { +size_t getPresentFenceShift(Period minFramePeriod) { + const bool isTwoVsyncsAhead = targetsVsyncsAhead<2>(minFramePeriod); + size_t shift = 0; + if (isTwoVsyncsAhead) { + shift = static_cast(expectedFrameDuration.ns() / minFramePeriod.ns()); + if (shift >= mPresentFences.size()) { + shift = mPresentFences.size() - 1; + } + } + return shift; +} +} // namespace + namespace android::scheduler { FrameTarget::FrameTarget(const std::string& displayLabel) @@ -30,7 +44,7 @@ FrameTarget::FrameTarget(const std::string& displayLabel) TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const { // TODO(b/267315508): Generalize to N VSYNCs. - const int shift = static_cast(targetsVsyncsAhead<2>(minFramePeriod)); + const size_t shift = getPresentFenceShift(minFramePeriod); return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift); } @@ -38,8 +52,10 @@ FenceTimePtr FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) { return pastVsyncTimePtr(); } - const size_t i = static_cast(targetsVsyncsAhead<2>(minFramePeriod)); - return mPresentFences[i].fenceTime; + + const size_t shift = getPresentFenceShift(minFramePeriod); + ATRACE_FORMAT("mPresentFences shift=%zu", shift); + return mPresentFences[shift].fenceTime; } bool FrameTarget::wouldPresentEarly(Period minFramePeriod) const { @@ -151,7 +167,9 @@ FenceTimePtr FrameTargeter::setPresentFence(sp presentFence, FenceTimePtr if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) { addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime); } else { - mPresentFences[1] = mPresentFences[0]; + for (size_t i = mPreviousPresentFences.size()-1; i >= 1; i--) { + mPresentFences[i] = mPresentFences[i-1]; + } mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime}; } return presentFenceTime; -- GitLab From 0ba3f3e42af40f4d0219e124efba78152b7298b3 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 21 Aug 2024 18:35:32 +0000 Subject: [PATCH 464/465] DO NOT MERGE Revert "DO NOT MERGE Extend mPreviousPresentFences for high refresh rate" This reverts commit 73e9ae304000410d942d13e733ddc1ab303fdfd3. Reason for revert: http://b/361358860 Change-Id: I7d38f19a5514ff70ea43a3533b45f3c6bf4e83c9 --- .../include/scheduler/FrameTargeter.h | 3 +-- .../Scheduler/src/FrameTargeter.cpp | 26 +++---------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h index 638a6fb464..d37d2dc51b 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h @@ -106,8 +106,7 @@ protected: FenceTimePtr fenceTime = FenceTime::NO_FENCE; TimePoint expectedPresentTime = TimePoint(); }; - // size should be longest sf-duration / shortest vsync period and round up - std::array mPresentFences; // currently consider 166hz. + std::array mPresentFences; utils::RingBuffer mFenceWithFenceTimes; TimePoint mLastSignaledFrameTime; diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp index 063f1b3d1d..badd21ef86 100644 --- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp +++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp @@ -20,20 +20,6 @@ #include #include -namespace { -size_t getPresentFenceShift(Period minFramePeriod) { - const bool isTwoVsyncsAhead = targetsVsyncsAhead<2>(minFramePeriod); - size_t shift = 0; - if (isTwoVsyncsAhead) { - shift = static_cast(expectedFrameDuration.ns() / minFramePeriod.ns()); - if (shift >= mPresentFences.size()) { - shift = mPresentFences.size() - 1; - } - } - return shift; -} -} // namespace - namespace android::scheduler { FrameTarget::FrameTarget(const std::string& displayLabel) @@ -44,7 +30,7 @@ FrameTarget::FrameTarget(const std::string& displayLabel) TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const { // TODO(b/267315508): Generalize to N VSYNCs. - const size_t shift = getPresentFenceShift(minFramePeriod); + const int shift = static_cast(targetsVsyncsAhead<2>(minFramePeriod)); return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift); } @@ -52,10 +38,8 @@ FenceTimePtr FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) { return pastVsyncTimePtr(); } - - const size_t shift = getPresentFenceShift(minFramePeriod); - ATRACE_FORMAT("mPresentFences shift=%zu", shift); - return mPresentFences[shift].fenceTime; + const size_t i = static_cast(targetsVsyncsAhead<2>(minFramePeriod)); + return mPresentFences[i].fenceTime; } bool FrameTarget::wouldPresentEarly(Period minFramePeriod) const { @@ -167,9 +151,7 @@ FenceTimePtr FrameTargeter::setPresentFence(sp presentFence, FenceTimePtr if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) { addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime); } else { - for (size_t i = mPreviousPresentFences.size()-1; i >= 1; i--) { - mPresentFences[i] = mPresentFences[i-1]; - } + mPresentFences[1] = mPresentFences[0]; mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime}; } return presentFenceTime; -- GitLab From 68e90f1b4ef16e1a7c1af903fee598369cd890d6 Mon Sep 17 00:00:00 2001 From: Ady Abraham Date: Wed, 21 Aug 2024 19:15:57 +0000 Subject: [PATCH 465/465] DO NOT MERGE Extend mPreviousPresentFences for high refresh rate To provide hardware enough time to display a frame under high refresh rate with long sf-duration, we extend mPreviousPresentFences by storing extra slots. We check proper present fence at different moment according to vsync period and sf-duration. Bug: 241193992 Bug: 361005063 Change-Id: Ibe9b06e590ed420f29c790188dee11674fca5a2e Merged-In: Ib603c0fd29a3e4cb2fcd99dca59bbd5bfb55a787 --- .../Scheduler/include/scheduler/FrameTargeter.h | 15 ++++++++++++++- .../Scheduler/src/FrameTargeter.cpp | 12 ++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h index d37d2dc51b..2c397bd18d 100644 --- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h +++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h @@ -106,7 +106,8 @@ protected: FenceTimePtr fenceTime = FenceTime::NO_FENCE; TimePoint expectedPresentTime = TimePoint(); }; - std::array mPresentFences; + // size should be longest sf-duration / shortest vsync period and round up + std::array mPresentFences; // currently consider 166hz. utils::RingBuffer mFenceWithFenceTimes; TimePoint mLastSignaledFrameTime; @@ -131,6 +132,18 @@ private: } return pastFenceTimePtr; } + + size_t getPresentFenceShift(Period minFramePeriod) const { + const bool isTwoVsyncsAhead = targetsVsyncsAhead<2>(minFramePeriod); + size_t shift = 0; + if (isTwoVsyncsAhead) { + shift = static_cast(expectedFrameDuration().ns() / minFramePeriod.ns()); + if (shift >= mPresentFences.size()) { + shift = mPresentFences.size() - 1; + } + } + return shift; + } }; // Computes a display's per-frame metrics about past/upcoming targeting of present deadlines. diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp index badd21ef86..7036e677a0 100644 --- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp +++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp @@ -30,7 +30,7 @@ FrameTarget::FrameTarget(const std::string& displayLabel) TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const { // TODO(b/267315508): Generalize to N VSYNCs. - const int shift = static_cast(targetsVsyncsAhead<2>(minFramePeriod)); + const size_t shift = getPresentFenceShift(minFramePeriod); return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift); } @@ -38,8 +38,10 @@ FenceTimePtr FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) { return pastVsyncTimePtr(); } - const size_t i = static_cast(targetsVsyncsAhead<2>(minFramePeriod)); - return mPresentFences[i].fenceTime; + + const size_t shift = getPresentFenceShift(minFramePeriod); + ATRACE_FORMAT("mPresentFences shift=%zu", shift); + return mPresentFences[shift].fenceTime; } bool FrameTarget::wouldPresentEarly(Period minFramePeriod) const { @@ -151,7 +153,9 @@ FenceTimePtr FrameTargeter::setPresentFence(sp presentFence, FenceTimePtr if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) { addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime); } else { - mPresentFences[1] = mPresentFences[0]; + for (size_t i = mPresentFences.size()-1; i >= 1; i--) { + mPresentFences[i] = mPresentFences[i-1]; + } mPresentFences[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime}; } return presentFenceTime; -- GitLab